Files
takana/crates/takana-build/tests/hidratacion_no_escapa.rs
T
Sergio b818f5249f takana etapa 5d: comentarios de crates, CLAUDE.md y el skill de granja
205 lineas en 73 ficheros de crates, mas la prosa de CLAUDE.md y del skill,
que se me habian quedado afuera de los barridos anteriores (no eran ni recetas
ni docs/ ni scripts/).

EL BARRIDO ANCHO ESTUVO A UN COMMIT DE ROMPER EL CORPUS ENTERO.

El primer intento reescribia los .rs completos, no solo los comentarios. Entre
las lineas de codigo que tocaba estaban SIETE etiquetas de separacion de
dominio, que son ENTRADA DE HASH:

  b"hammer-tree-v1"        <- el prefijo de ArtifactHash::of_tree (hash.rs:60)
  b"hammer-seed-v1"           la funcion que hashea TODOS los artefactos:
  b"hammer-stage1-rootfs-v2"  cambiarla mueve los 4750 hashes del store
  b"hammer-product-rootfs-v3"
  b"hammer-product-attested-v2"
  b"hammer-builder-rootfs-v1"
  b"hammer-attest-dev-rootkey-0001!!"  <- clave raiz de atestacion, [u8;32]

Revertido y rehecho solo sobre comentarios, esquivando ademas las cadenas
crudas de Rust (r#"..."#) porque el SYSTEM_PROMPT del traductor tiene lineas
que empiezan como comentario.

Controles: las 7 etiquetas siguen ahi, el diff toca CERO lineas de codigo,
605 tests en verde y el hash de zlib sigue en b3:dc363f26.

La leccion es la misma de toda esta etapa: un literal que parece prosa puede
ser entrada de hash, y la unica forma de saberlo es mirar donde se usa.
2026-09-09 19:38:33 +00:00

77 lines
3.5 KiB
Rust

//! La hidratación no escribe fuera de su root, aunque el FHS ya traiga un symlink que salga.
//!
//! EL FALLO (medido 2026-08-30, antes de `no_escapa`): hidratar es `target_fhs.join(rel)` y
//! escribir por ruta. Un artefacto que trae `usr/share/pkg → /algún/lado` deja ese symlink puesto
//! —los symlinks se replican literales, y así debe ser— y la hidratación SIGUIENTE escribía
//! `usr/share/pkg/archivo` **fuera del root**, devolviendo `Ok(1)`. Con `takana hydrate --into`
//! sobre una imagen, eso es escribir en el sistema anfitrión diciendo que todo fue bien.
//!
//! El corpus no lo disparaba (de 160 symlinks absolutos ninguno apunta a un directorio, y de
//! ~42 000 relativos ninguno sale de su artefacto), así que esto es una mina desactivada, no un
//! incendio apagado. Por eso el test: lo que no dispara nadie tampoco lo nota nadie.
use std::path::Path;
use takana_build::hydrate::hydrate;
use takana_core::LinkMode;
/// Artefacto que trae `usr/share/pkg` como symlink a `destino`.
fn artefacto_con_symlink(dir: &Path, destino: &Path) {
std::fs::create_dir_all(dir.join("usr/share")).unwrap();
std::os::unix::fs::symlink(destino, dir.join("usr/share/pkg")).unwrap();
}
/// Artefacto que trae un fichero DEBAJO de esa misma ruta.
fn artefacto_con_fichero(dir: &Path) {
std::fs::create_dir_all(dir.join("usr/share/pkg")).unwrap();
std::fs::write(dir.join("usr/share/pkg/archivo"), b"contenido").unwrap();
}
#[test]
fn un_symlink_del_fhs_que_sale_del_root_no_deja_escribir() {
let fuera = tempfile::tempdir().unwrap();
let fhs = tempfile::tempdir().unwrap();
let a = tempfile::tempdir().unwrap();
let b = tempfile::tempdir().unwrap();
artefacto_con_symlink(a.path(), fuera.path());
hydrate(a.path(), fhs.path(), LinkMode::Static, None).unwrap();
artefacto_con_fichero(b.path());
let r = hydrate(b.path(), fhs.path(), LinkMode::Static, None);
assert!(r.is_err(), "hidratar a través del symlink tiene que FALLAR, no devolver Ok");
let msg = format!("{}", r.unwrap_err());
assert!(msg.contains("se saldría del root"), "el error tiene que decir qué pasó: {msg}");
assert!(
!fuera.path().join("archivo").exists(),
"ESCAPÓ: el fichero se escribió fuera del target_fhs"
);
}
/// El contrapeso: un symlink de directorio que se queda DENTRO tiene que seguir funcionando.
/// Es el caso de usr-merge (`/lib → usr/lib`) y el de los temas de iconos (`22@2x → 22`), o sea
/// la mayoría de los symlinks de directorio que hay en el corpus. Un guardián que los rompiera
/// sería peor que el agujero que tapa.
#[test]
fn un_symlink_que_se_queda_dentro_sigue_hidratando() {
let fhs = tempfile::tempdir().unwrap();
let a = tempfile::tempdir().unwrap();
let b = tempfile::tempdir().unwrap();
// `usr/share/pkg → ../otro` (relativo y dentro), con el destino existiendo.
std::fs::create_dir_all(a.path().join("usr/otro")).unwrap();
std::fs::write(a.path().join("usr/otro/.marca"), b"x").unwrap();
std::fs::create_dir_all(a.path().join("usr/share")).unwrap();
std::os::unix::fs::symlink("../otro", a.path().join("usr/share/pkg")).unwrap();
hydrate(a.path(), fhs.path(), LinkMode::Static, None).unwrap();
artefacto_con_fichero(b.path());
let r = hydrate(b.path(), fhs.path(), LinkMode::Static, None);
assert!(r.is_ok(), "un symlink que no sale del root no puede bloquear la hidratación: {r:?}");
assert!(
fhs.path().join("usr/otro/archivo").exists(),
"el fichero tiene que aterrizar donde apunta el symlink, dentro del root"
);
}