diff --git a/docs/pull-requests/PR-004-runtime-vfs-path-traversal-hardening.md b/docs/pull-requests/PR-004-runtime-vfs-path-traversal-hardening.md deleted file mode 100644 index b348a2b0..00000000 --- a/docs/pull-requests/PR-004-runtime-vfs-path-traversal-hardening.md +++ /dev/null @@ -1,257 +0,0 @@ -# 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. diff --git a/docs/pull-requests/PR-005-host-audio-fallible-init-and-headless-tolerance.md b/docs/pull-requests/PR-005-host-audio-fallible-init-and-headless-tolerance.md deleted file mode 100644 index 8423dd41..00000000 --- a/docs/pull-requests/PR-005-host-audio-fallible-init-and-headless-tolerance.md +++ /dev/null @@ -1,87 +0,0 @@ -# PR-005: Host Audio Fallible Init and Headless Tolerance - -## Briefing - -O host desktop atual assume que audio de saida esta sempre disponivel e funcional. Em ambientes headless, CI, VMs remotas ou sistemas sem device compativel, a inicializacao usa `expect(...)` e encerra o processo. - -Este PR torna a pilha de audio tolerante a falhas de infraestrutura. O runtime deve continuar operando sem audio quando o host nao puder inicializar a saida sonora. - -## Problema - -- `default_output_device()` pode retornar `None`. -- `build_output_stream(...)` pode falhar por formato/configuracao. -- `stream.play()` pode falhar por indisponibilidade do backend. -- Todas essas falhas hoje derrubam o binario com `panic!`. - -## Escopo - -- Trocar inicializacao panica por API fallible. -- Permitir degradacao controlada para "sem audio". -- Manter telemetria/log suficiente para diagnostico. -- Cobrir o fluxo em testes de unidade onde viavel. - -## Fora de Escopo - -- Rework do mixer. -- Selecao automatica de formatos de audio mais sofisticada. -- Hot-reload de device de audio. -- Simulacao de audio em buffer offline. - -## Abordagem - -1. Mudar `HostAudio::init` para retornar `Result<(), HostAudioError>` ou tipo equivalente. -2. Representar explicitamente o estado "audio indisponivel": - - `producer = None` - - `stream = None` - - stats continuam funcionando sem crash. -3. No chamador, registrar aviso e seguir execucao sem audio. -4. Garantir que `send_commands` e `update_stats` sejam no-op seguros quando o audio nao estiver ativo. - -## Algoritmo - -### Inicializacao - -1. Buscar `default_output_device`. -2. Se nao existir: - - retornar erro de inicializacao controlado. -3. Tentar construir `StreamConfig` e `build_output_stream`. -4. Se falhar: - - retornar erro controlado sem panicar. -5. Tentar `play()`. -6. Se falhar: - - retornar erro controlado sem panicar. -7. Apenas em caso de sucesso preencher `producer`, `perf_consumer` e `_stream`. - -### Degradacao - -1. O host chama `init`. -2. Se `Ok`, audio ativo. -3. Se `Err`, loga a falha e continua sem audio. -4. O loop principal segue responsivo e funcional. - -## Criterios de Aceite - -- O runtime nao entra em `panic!` quando nao existe device de audio. -- O runtime nao entra em `panic!` quando `build_output_stream` falha. -- O runtime nao entra em `panic!` quando `play()` falha. -- `send_commands` e `update_stats` continuam seguros sem stream ativo. -- O host continua executando cartridge em modo sem audio. -- Logs deixam claro por que o audio foi desativado. - -## Tests - -- Introduzir testes de unidade para o estado sem audio: - - `send_commands` nao falha quando `producer` e `None`; - - `update_stats` nao falha quando `perf_consumer` e `None`. -- Se a estrutura atual permitir injecao de dependencia: - - testar caminho `no output device`; - - testar falha na criacao do stream; - - testar falha no `play`. -- Se injecao ainda nao existir, criar uma camada minima de abstracao para tornar esses cenarios testaveis sem depender do hardware real. -- Rodar: - - `cargo test -p prometeu-host-desktop-winit` - - `cargo test --workspace` - -## Risco - -Baixo. A mudanca principal e de robustez operacional. O maior cuidado e nao esconder a falha completamente; o modo sem audio precisa ser explicito em logs. diff --git a/docs/pull-requests/PR-006-cli-preserve-child-exit-status.md b/docs/pull-requests/PR-006-cli-preserve-child-exit-status.md deleted file mode 100644 index 8db2a506..00000000 --- a/docs/pull-requests/PR-006-cli-preserve-child-exit-status.md +++ /dev/null @@ -1,72 +0,0 @@ -# PR-006: CLI Preserve Child Exit Status - -## Briefing - -O wrapper `prometeu` despacha para outros binarios do ecossistema. Hoje, se o processo filho terminar sem exit code convencional, o CLI usa `unwrap_or(0)` e pode reportar sucesso mesmo quando houve abort, sinal ou encerramento anormal. - -Este PR corrige a propagacao de status do processo filho para tornar o CLI confiavel em automacao, CI e uso por scripts. - -## Problema - -- `status.code()` pode retornar `None`. -- `unwrap_or(0)` transforma falha anormal em sucesso. -- Isso mascara erros reais em `run`, `debug`, `build` e `verify`. - -## Escopo - -- Preservar corretamente o resultado do processo filho. -- Melhorar a mensagem de erro quando nao houver codigo numerico. -- Cobrir o comportamento com testes. - -## Fora de Escopo - -- Reestruturar todo o dispatcher. -- Implementar subcomandos ainda nao existentes. -- Alterar o protocolo entre CLI e bins filhos. - -## Abordagem - -1. Remover `unwrap_or(0)` no ponto de saida. -2. Tratar dois casos: - - filho retorna exit code numerico: repassar exatamente; - - filho termina sem codigo numerico: encerrar com codigo nao-zero e mensagem clara. -3. Se necessario, extrair a traducao de `ExitStatus` para uma funcao pequena e testavel. - -## Algoritmo - -Entrada: `ExitStatus` do processo filho. - -Passos: - -1. Chamar `status.code()`. -2. Se retornar `Some(code)`: - - encerrar o wrapper com `code`. -3. Se retornar `None`: - - escrever mensagem de erro indicando encerramento anormal; - - encerrar o wrapper com `1`. - -## Criterios de Aceite - -- Se o processo filho sair com `0`, o wrapper sai com `0`. -- Se o processo filho sair com codigo nao-zero, o wrapper replica o mesmo codigo. -- Se o processo filho terminar sem codigo numerico, o wrapper sai com codigo nao-zero. -- O comportamento fica coberto por teste unitario. - -## Tests - -- Extrair funcao pura ou quase pura para mapear `ExitStatus -> i32` e testá-la. -- Cobrir: - - `0`; - - `1`; - - outro codigo nao-zero; - - status sem code numerico quando a plataforma permitir criar esse caso em teste. -- Se o caso `None` depender de plataforma: - - encapsular sob `cfg(unix)` ou `cfg(windows)` conforme apropriado; - - manter ao menos o contrato da funcao testado por unidade. -- Rodar: - - `cargo test -p prometeu-cli` - - `cargo test --workspace` - -## Risco - -Muito baixo. A mudanca corrige semantica operacional do CLI e tende a melhorar observabilidade sem alterar a logica de dispatch.