From 239f96263d334dbd20a819a30226af5bc238c25c Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 23 Oct 2026 08:07:33 -0300 Subject: [PATCH 1/5] =?UTF-8?q?Teste=20de=20XFS=20com=20exclus=C3=A3o=20fe?= =?UTF-8?q?ita=20pelo=20kernel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/make-xfs-real-deletion.sh (precisa de root) monta uma imagem XFS por loop, grava arquivos, apaga alguns e desmonta, deixando o hash de cada um em expected.tsv. O teste confere que cada arquivo apagado volta pelo nome, na pasta certa e com o conteúdo original, e que os que ficaram não aparecem como apagados. O CI do Linux gera a imagem com sudo e exige o teste. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 5 +- crates/guara-core/tests/xfs.rs | 97 +++++++++++++++++++++++++++++++ scripts/make-xfs-real-deletion.sh | 57 ++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100755 scripts/make-xfs-real-deletion.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e65dc2..06e8216 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,10 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y mtools dosfstools exfatprogs ntfs-3g e2fsprogs imagemagick zip + sudo apt-get install -y mtools dosfstools exfatprogs ntfs-3g e2fsprogs xfsprogs imagemagick zip + - name: Imagem XFS com exclusão feita pelo kernel (Linux) + if: runner.os == 'Linux' + run: sudo scripts/make-xfs-real-deletion.sh - name: Testes (com imagens reais FAT32/NTFS/ext4 no Linux) shell: bash run: | diff --git a/crates/guara-core/tests/xfs.rs b/crates/guara-core/tests/xfs.rs index 9062597..49bed9b 100644 --- a/crates/guara-core/tests/xfs.rs +++ b/crates/guara-core/tests/xfs.rs @@ -268,3 +268,100 @@ fn reference_image_is_recognized() { let l = c.layout.unwrap(); assert_eq!(l.partitions[0].fs, FsKind::Xfs); } + +/// Exclusão feita pelo kernel (scripts/make-xfs-real-deletion.sh, precisa de root): cada arquivo +/// apagado precisa voltar pelo nome, na pasta certa, com o conteúdo original no começo; os que +/// ficaram não podem aparecer como apagados. Pulado quando a imagem não foi gerada. +#[test] +fn real_kernel_deletion_is_recovered() { + let dir = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/testdata/xfs-real-delete"); + let (img, tsv) = (dir.join("disk.img"), dir.join("expected.tsv")); + if !(img.exists() && tsv.exists()) { + assert!( + std::env::var("GUARA_REQUIRE_FIXTURES").is_err(), + "faltou a imagem: sudo scripts/make-xfs-real-deletion.sh" + ); + eprintln!("SKIP: rode sudo scripts/make-xfs-real-deletion.sh"); + return; + } + init(); + let rows: Vec<(String, u64, String, bool)> = std::fs::read_to_string(&tsv) + .unwrap() + .lines() + .map(|l| { + let c: Vec<&str> = l.split('\t').collect(); + ( + c[0].to_string(), + c[1].parse().unwrap(), + c[2].to_string(), + c[3] == "deleted", + ) + }) + .collect(); + let c = scan_path( + &img, + &ScanOptions { + mode: ScanMode::Deep, + ..Default::default() + }, + ); + let xfs: Vec<&Found> = c.found.iter().filter(|f| f.origin == Origin::Xfs).collect(); + let listing = || { + xfs.iter() + .map(|f| { + format!( + "{}/{} ({} bytes, {:?})", + f.dir.as_deref().unwrap_or(""), + f.name, + f.size, + f.confidence + ) + }) + .collect::>() + }; + let out = tempfile::tempdir().unwrap(); + for (rel, size, sha, deleted) in &rows { + let (dirname, name) = match rel.rsplit_once('/') { + Some((d, n)) => (format!("/{d}"), n), + None => (String::new(), rel.as_str()), + }; + let hit = xfs + .iter() + .find(|f| f.name == name && f.dir.as_deref().unwrap_or("") == dirname); + if !*deleted { + assert!( + hit.is_none(), + "{rel} não foi apagado, mas apareceu como recuperável: {:?}", + listing() + ); + continue; + } + let f = hit.unwrap_or_else(|| { + panic!( + "{rel} apagado pelo kernel não foi encontrado; achados: {:?}", + listing() + ) + }); + let rep = recover_all(&img, std::slice::from_ref(*f), &out.path().join(name)); + assert!(rep.failed.is_empty(), "{rel}: {:?}", rep.failed); + let rp = PathBuf::from(&rep.recovered[0].path); + let got = std::fs::read(if rp.is_absolute() { + rp + } else { + out.path().join(name).join(rp) + }) + .unwrap(); + // o tamanho exato se perde na exclusão: o arquivo vem com os blocos inteiros + assert!( + got.len() as u64 >= *size, + "{rel}: veio menor ({} < {size})", + got.len() + ); + assert_eq!( + sha256(&got[..*size as usize]), + *sha, + "{rel}: o conteúdo inicial difere do original" + ); + } +} diff --git a/scripts/make-xfs-real-deletion.sh b/scripts/make-xfs-real-deletion.sh new file mode 100755 index 0000000..1927461 --- /dev/null +++ b/scripts/make-xfs-real-deletion.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Cria uma imagem XFS com exclusão REAL (feita pelo kernel), para validar a recuperação de XFS +# sem simulação. Precisa de root, porque monta a imagem por loop: sudo scripts/make-xfs-real-deletion.sh +# +# Saída em target/testdata/xfs-real-delete/: disk.img e expected.tsv (nome, tamanho, sha256 e se +# o arquivo foi apagado). O teste em crates/guara-core/tests/xfs.rs usa esses dois arquivos e é +# pulado quando eles não existem. +set -euo pipefail +[ "$(id -u)" = 0 ] || { echo "rode com sudo: sudo $0" >&2; exit 1; } +root="$(cd "$(dirname "$0")/.." && pwd)" +out="${1:-$root/target/testdata/xfs-real-delete}" +mkdir -p "$out" +img="$out/disk.img" +rm -f "$img" +truncate -s 64M "$img" +mkfs.xfs -q -f "$img" +mnt="$(mktemp -d)" +cleanup() { mountpoint -q "$mnt" && umount "$mnt"; rmdir "$mnt" 2>/dev/null || true; } +trap cleanup EXIT +mount -o loop "$img" "$mnt" +python3 - "$mnt" "$out/expected.tsv" <<'PY' +import hashlib, os, sys +mnt, tsv = sys.argv[1], sys.argv[2] +def data(seed, n): + x, out = seed | 1, bytearray() + for _ in range(n): + x ^= (x << 13) & 0xFFFFFFFFFFFFFFFF; x ^= x >> 7; x ^= (x << 17) & 0xFFFFFFFFFFFFFFFF + out.append((x >> 24) & 0xFF) + return bytes(out) +files = [ # (caminho, tamanho, semente, apagar?) + ("docs/relatorio.dat", 150000, 5, True), + ("docs/mantido.dat", 90000, 6, False), + ("fotos/foto1.bin", 300000, 7, True), + ("fotos/foto2.bin", 40000, 8, False), + ("raiz-apagada.txt", 20000, 9, True), +] +rows = [] +for rel, n, seed, gone in files: + p = os.path.join(mnt, rel) + os.makedirs(os.path.dirname(p), exist_ok=True) + d = data(seed, n) + with open(p, "wb") as f: + f.write(d) + rows.append((rel, n, hashlib.sha256(d).hexdigest(), "deleted" if gone else "live")) +os.sync() +for rel, n, seed, gone in files: + if gone: + os.remove(os.path.join(mnt, rel)) +os.sync() +with open(tsv, "w") as f: + for r in rows: + f.write("\t".join(map(str, r)) + "\n") +PY +sync +umount "$mnt" +[ -n "${SUDO_UID:-}" ] && chown -R "$SUDO_UID:${SUDO_GID:-$SUDO_UID}" "$out" +echo "ok: $img" From 418d0b9b905a0f93812c398cbba1c66b22876f2b Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 23 Oct 2026 07:55:18 -0300 Subject: [PATCH 2/5] =?UTF-8?q?Depend=C3=AAncias:=20ctutils=200.4.5=20(RUS?= =?UTF-8?q?TSEC-2026-0385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comparação em tempo constante corrigida na 0.4.4; o job de auditoria falhava. Co-Authored-By: Claude Sonnet 5.5 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0725c0..0f6513e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,9 +309,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +checksum = "38d28fb6aa7dd680b3ffd1a3e64e115c177bdba63c0ae7f9cd48129ff05c4fa0" dependencies = [ "cmov", ] From e5ae3ea5704da5a30616b70e62ea56f499b713f4 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 23 Oct 2026 08:31:42 -0300 Subject: [PATCH 3/5] XFS real: imagem de 320 MiB (o mkfs.xfs recente recusa menos de 300 MiB) Co-Authored-By: Claude Sonnet 5.5 --- scripts/make-xfs-real-deletion.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/make-xfs-real-deletion.sh b/scripts/make-xfs-real-deletion.sh index 1927461..7408c38 100755 --- a/scripts/make-xfs-real-deletion.sh +++ b/scripts/make-xfs-real-deletion.sh @@ -12,7 +12,7 @@ out="${1:-$root/target/testdata/xfs-real-delete}" mkdir -p "$out" img="$out/disk.img" rm -f "$img" -truncate -s 64M "$img" +truncate -s 320M "$img" # o mkfs.xfs recente recusa menos de 300 MiB mkfs.xfs -q -f "$img" mnt="$(mktemp -d)" cleanup() { mountpoint -q "$mnt" && umount "$mnt"; rmdir "$mnt" 2>/dev/null || true; } From 4d93d9d5127428b6a188ef47d86d1625973dd58d Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 23 Oct 2026 08:35:55 -0300 Subject: [PATCH 4/5] =?UTF-8?q?XFS:=20o=20teste=20com=20exclus=C3=A3o=20re?= =?UTF-8?q?al=20mostra=20o=20que=20funciona=20de=20verdade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No CI, com um XFS apagado pelo kernel, a recuperação por metadados não acha nada (o kernel zera o inode e as extensões); o carving recupera o conteúdo de arquivos com estrutura reconhecível. O teste de nome e pasta fica ignorado até o Guará ler o diário, e o roadmap e a documentação passam a dizer isso em vez de marcar o XFS como validado. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 4 +- crates/guara-core/tests/xfs.rs | 74 +++++++++++++++++++++++++++++-- docs/pt-BR/suporte.md | 2 +- docs/support.md | 2 +- scripts/make-xfs-real-deletion.sh | 9 ++-- 5 files changed, 80 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0359f6f..48f3c77 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ In a single scan, Guará does what tools like Recuva, PhotoRec, TestDisk and Disk Drill do separately: -- **File system metadata:** finds deleted files **with name, folder and date** on NTFS, FAT12/16/32, exFAT, ext2/3/4, HFS+, Btrfs, XFS and APFS. +- **File system metadata:** finds deleted files **with name, folder and date** on NTFS, FAT12/16/32, exFAT, ext2/3/4, HFS+, Btrfs and APFS (on XFS only the content comes back, by carving, see below). - **Structure-aware carving** in free space, damaged partitions and unpartitioned space. The detectors read the real structure of dozens of formats to learn the **exact size** and a **confidence** level. - **Encrypted volumes:** opens BitLocker, LUKS and FileVault 2 with your password or key and scans inside. - **RAID and LVM:** reassembles arrays from loose disks (RAID 0, 1, 4, 5, 6, 10, LVM2), working out level, stripe size and order by itself when there is no metadata. @@ -98,7 +98,7 @@ A desktop app (Tauri) in the style of a file manager, designed for when Guará f - **Overwritten data does not come back.** On an SSD with TRIM the disk may zero deleted blocks; Guará warns you, but it cannot work miracles. - **Fragments:** only JPEG (up to three pieces) and MP4 without an index are reassembled. Other formats, if fragmented, come out truncated with low confidence. - **ReFS and Storage Spaces** are not supported (ReFS is only detected). -- **APFS:** no free-space map. **XFS and APFS:** deletion recovery tested only in simulation. +- **XFS:** on a real XFS (v5, kernel deletion) the inode and its extent list are wiped, so Guará recovers the **content** of files with a recognizable structure by carving, but not their name or folder; reading names needs the file system journal, which is not implemented. The old inode-based path only works on simulated deletions. **APFS:** deletion recovery tested only in simulation. - **NTFS:** compressed files, EFS and heavily fragmented records are skipped or marked as incomplete. - **exFAT:** validated with a synthetic image; real media are still missing. - **Phones:** Guará works with storage media and does **not** unlock devices or bypass passwords. diff --git a/crates/guara-core/tests/xfs.rs b/crates/guara-core/tests/xfs.rs index 49bed9b..7ff0e5d 100644 --- a/crates/guara-core/tests/xfs.rs +++ b/crates/guara-core/tests/xfs.rs @@ -269,11 +269,17 @@ fn reference_image_is_recognized() { assert_eq!(l.partitions[0].fs, FsKind::Xfs); } -/// Exclusão feita pelo kernel (scripts/make-xfs-real-deletion.sh, precisa de root): cada arquivo -/// apagado precisa voltar pelo nome, na pasta certa, com o conteúdo original no começo; os que -/// ficaram não podem aparecer como apagados. Pulado quando a imagem não foi gerada. +/// Meta: exclusão feita pelo kernel (scripts/make-xfs-real-deletion.sh, precisa de root): cada +/// arquivo apagado volta pelo nome, na pasta certa, com o conteúdo original no começo, e os que +/// ficaram não aparecem como apagados. +/// +/// HOJE FALHA, e é por isso que está `ignore`: num XFS v5 real o kernel zera o inode e a lista de +/// extensões ao apagar, então só a simulação (teste acima) deixa a metadados para ler. Recuperar +/// nome e conteúdo de um XFS real exige ler o diário (log) do sistema de arquivos. Rodar com +/// `cargo test -p guara-core --test xfs -- --ignored` para ver o estado. #[test] -fn real_kernel_deletion_is_recovered() { +#[ignore = "XFS real: o kernel apaga os metadados; falta ler o diário (ver ROADMAP)"] +fn real_kernel_deletion_names_are_recovered() { let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/testdata/xfs-real-delete"); let (img, tsv) = (dir.join("disk.img"), dir.join("expected.tsv")); @@ -365,3 +371,63 @@ fn real_kernel_deletion_is_recovered() { ); } } + +/// O que funciona hoje num XFS apagado de verdade: o conteúdo dos blocos livres continua lá e a +/// varredura profunda recupera, por carving, o arquivo com estrutura reconhecível (SQLite), com +/// o conteúdo idêntico ao original. O nome e a pasta se perdem. +#[test] +fn real_kernel_deletion_content_is_recovered_by_carving() { + let dir = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/testdata/xfs-real-delete"); + let (img, tsv) = (dir.join("disk.img"), dir.join("expected.tsv")); + if !(img.exists() && tsv.exists()) { + assert!( + std::env::var("GUARA_REQUIRE_FIXTURES").is_err(), + "faltou a imagem: sudo scripts/make-xfs-real-deletion.sh" + ); + eprintln!("SKIP: rode sudo scripts/make-xfs-real-deletion.sh"); + return; + } + init(); + let want = std::fs::read_to_string(&tsv) + .unwrap() + .lines() + .map(|l| l.split('\t').collect::>()) + .find(|c| c[0].ends_with(".sqlite")) + .map(|c| (c[1].parse::().unwrap(), c[2].to_string())) + .expect("o SQLite apagado consta do expected.tsv"); + let c = scan_path( + &img, + &ScanOptions { + mode: ScanMode::Deep, + ..Default::default() + }, + ); + let out = tempfile::tempdir().unwrap(); + let mut seen = vec![]; + for (i, f) in c.found.iter().enumerate() { + if f.size as usize != want.0 { + continue; + } + let rep = recover_all( + &img, + std::slice::from_ref(f), + &out.path().join(i.to_string()), + ); + let rp = PathBuf::from(&rep.recovered[0].path); + let p = if rp.is_absolute() { + rp + } else { + out.path().join(i.to_string()).join(rp) + }; + seen.push(sha256_file(&p)); + } + assert!( + seen.contains(&want.1), + "o SQLite apagado não voltou idêntico; achados do mesmo tamanho: {seen:?}; todos: {:?}", + c.found + .iter() + .map(|f| (&f.name, f.size, f.origin)) + .collect::>() + ); +} diff --git a/docs/pt-BR/suporte.md b/docs/pt-BR/suporte.md index b2f9a2d..39586f8 100644 --- a/docs/pt-BR/suporte.md +++ b/docs/pt-BR/suporte.md @@ -20,7 +20,7 @@ MBR e GPT. Quando a tabela falha, o Guará procura volumes por assinatura (NTFS, | ext2/3/4 | ✅ | Inodes apagados, cópias antigas no diário (ext4), mapa de blocos livres | | | HFS+ | ✅ | Sobras de nós da árvore de catálogo, diário | | | Btrfs | ✅ | Versões antigas das árvores; extensões zlib, zstd e lzo | | -| XFS | ✅ | Inodes liberados, nomes de entradas liberadas, espaço livre por grupo | Exclusão testada só em simulação | +| XFS | 🟡 | Inodes liberados, nomes de entradas liberadas e espaço livre por grupo (funciona na exclusão simulada); conteúdo por carving | Testado no CI com exclusão real do kernel: o kernel zera o inode e as extensões, então nome e pasta se perdem e só o carving recupera o conteúdo. Falta ler o diário | | APFS | 🟡 | Contêiner, mapa de objetos, árvore de arquivos, instantâneos, nós antigos | Sem mapa de espaço livre; exclusão testada só em simulação | | ReFS | ❌ | Apenas detectado | Recuperação por carving | | Storage Spaces | ❌ | Não suportado | | diff --git a/docs/support.md b/docs/support.md index 5b61db7..21ae5cd 100644 --- a/docs/support.md +++ b/docs/support.md @@ -20,7 +20,7 @@ MBR and GPT. When the table fails, Guará searches for volumes by signature (NTF | ext2/3/4 | ✅ | Deleted inodes, old copies in the journal (ext4), free block map | | | HFS+ | ✅ | Leftovers of catalog tree nodes, journal | | | Btrfs | ✅ | Old versions of the trees; zlib, zstd and lzo extents | | -| XFS | ✅ | Freed inodes, names of freed entries, free space per group | Deletion tested only in simulation | +| XFS | 🟡 | Freed inodes, names of freed entries and free space per group (works on simulated deletion); content by carving | Tested with real kernel deletion in CI: the kernel wipes the inode and extents, so names and folders are lost and only carving recovers content. Journal reading is missing | | APFS | 🟡 | Container, object map, file tree, snapshots, old nodes | No free-space map; deletion tested only in simulation | | ReFS | ❌ | Only detected | Recovery through carving | | Storage Spaces | ❌ | Not supported | | diff --git a/scripts/make-xfs-real-deletion.sh b/scripts/make-xfs-real-deletion.sh index 7408c38..cd0d39f 100755 --- a/scripts/make-xfs-real-deletion.sh +++ b/scripts/make-xfs-real-deletion.sh @@ -18,9 +18,9 @@ mnt="$(mktemp -d)" cleanup() { mountpoint -q "$mnt" && umount "$mnt"; rmdir "$mnt" 2>/dev/null || true; } trap cleanup EXIT mount -o loop "$img" "$mnt" -python3 - "$mnt" "$out/expected.tsv" <<'PY' +python3 - "$mnt" "$out/expected.tsv" "$root/crates/guara-core/tests/data/exemplo.sqlite" <<'PY' import hashlib, os, sys -mnt, tsv = sys.argv[1], sys.argv[2] +mnt, tsv, sqlite = sys.argv[1], sys.argv[2], sys.argv[3] def data(seed, n): x, out = seed | 1, bytearray() for _ in range(n): @@ -34,11 +34,14 @@ files = [ # (caminho, tamanho, semente, apagar?) ("fotos/foto2.bin", 40000, 8, False), ("raiz-apagada.txt", 20000, 9, True), ] +# um arquivo com estrutura reconhecível (SQLite), para a recuperação por carving +real = open(sqlite, "rb").read() rows = [] +files.append(("dados/exemplo.sqlite", len(real), 0, True)) for rel, n, seed, gone in files: p = os.path.join(mnt, rel) os.makedirs(os.path.dirname(p), exist_ok=True) - d = data(seed, n) + d = real if rel.endswith('.sqlite') else data(seed, n) with open(p, "wb") as f: f.write(d) rows.append((rel, n, hashlib.sha256(d).hexdigest(), "deleted" if gone else "live")) From bbb251aacbbceb1ed22b6caab113421a144f3942 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Fri, 23 Oct 2026 08:36:06 -0300 Subject: [PATCH 5/5] =?UTF-8?q?Roadmap:=20XFS=20=F0=9F=9F=A1=20depois=20do?= =?UTF-8?q?=20teste=20com=20exclus=C3=A3o=20real?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- ROADMAP.md | 7 ++++--- ROADMAP.pt-BR.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 5c24832..a15a7ad 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -40,12 +40,13 @@ Details of each version in the [CHANGELOG](CHANGELOG.md). | ext2/3/4 | ✅ | Deleted inodes and old copies in the journal | | HFS+ | ✅ | Tree node leftovers and journal | | Btrfs | ✅ | Old versions of the trees; zlib, zstd and lzo extents | -| XFS | ✅ | Freed inodes and names of freed entries. Tested only with simulated deletion | +| XFS | 🟡 | Freed inodes and names of freed entries work on simulated deletion only. With real kernel deletion (tested in CI) the metadata is wiped: content comes back by carving, names do not. **Journal reading is missing** | | APFS | 🟡 | Container, object map, free-space map (spaceman and bitmaps), snapshots and old nodes. Deletion tested only in simulation; containers with chunk-info address blocks (large ones) fall back to scanning the whole volume | | ReFS | 🔜 | Only detected today; recovery depends on carving. Reference image missing | | Storage Spaces | 🔜 | Not done. Reference image missing | | NTFS: compressed attributes, EFS, attribute list | 🔜 | Marked as incomplete today | -| XFS and APFS validation with real deletion | 🔜 | Replace the simulation with images from real devices | +| XFS: names and folders after real deletion (read the journal) | 🔜 | The CI test exists and is ignored until this lands | +| APFS validation with real deletion | 🔜 | Needs an image from a real Mac | ### 2. Encrypted volumes @@ -165,7 +166,7 @@ External validation is what makes a report accepted. Without it, Guará is good No dates, in the order that makes the most sense. **1.3: trust and custody** -Signed manifest, XFS and APFS validation with real deletions, tests with real images on Windows and macOS. +Signed manifest, XFS journal reading, APFS validation with real deletions, tests with real images on Windows and macOS. **1.4: analysis** NSRL and hash lists, search in file content, unified timeline. diff --git a/ROADMAP.pt-BR.md b/ROADMAP.pt-BR.md index 88e6fa7..0518bcf 100644 --- a/ROADMAP.pt-BR.md +++ b/ROADMAP.pt-BR.md @@ -40,12 +40,13 @@ Detalhes de cada versão no [CHANGELOG](CHANGELOG.pt-BR.md). | ext2/3/4 | ✅ | Inodes apagados e cópias antigas no diário | | HFS+ | ✅ | Sobras de nós da árvore e diário | | Btrfs | ✅ | Versões antigas das árvores; extensões zlib, zstd e lzo | -| XFS | ✅ | Inodes liberados e nomes de entradas liberadas. Testado só com exclusão simulada | +| XFS | 🟡 | Inodes liberados e nomes de entradas liberadas só funcionam na exclusão simulada. Com exclusão real do kernel (testada no CI) os metadados são zerados: o conteúdo volta por carving, o nome não. **Falta ler o diário** | | APFS | 🟡 | Contêiner, mapa de objetos, mapa de espaço livre (spaceman e bitmaps), instantâneos e nós antigos. Exclusão testada só em simulação; contêineres com blocos de endereço de chunk-info (grandes) voltam a varrer o volume inteiro | | ReFS | 🔜 | Hoje só é detectado; a recuperação depende do carving. Falta imagem de referência | | Storage Spaces | 🔜 | Não feito. Falta imagem de referência | | NTFS: atributos comprimidos, EFS, lista de atributos | 🔜 | Hoje marcados como incompletos | -| Validação de XFS e APFS com exclusão real | 🔜 | Trocar a simulação por imagens de dispositivos reais | +| XFS: nomes e pastas após exclusão real (ler o diário) | 🔜 | O teste no CI existe e fica ignorado até isso entrar | +| Validação de APFS com exclusão real | 🔜 | Precisa de imagem de um Mac de verdade | ### 2. Volumes cifrados @@ -165,7 +166,7 @@ A validação externa é o que faz um laudo ser aceito. Sem ela, o Guará serve Sem datas, na ordem em que fazem mais sentido. **1.3: confiança e custódia** -Manifesto assinado, validação de XFS e APFS com exclusões reais, testes com imagens reais no Windows e macOS. +Manifesto assinado, leitura do diário do XFS, validação de APFS com exclusões reais, testes com imagens reais no Windows e macOS. **1.4: análise** NSRL e listas de hashes, busca em conteúdo de arquivos, linha do tempo unificada.