turbo
ec358fa4 - fix: Keep turbo setup's tools working after a filesystem restore (#14983)

Commit
2 days ago
fix: Keep turbo setup's tools working after a filesystem restore (#14983) `turbo run` and `turbo exec` now keep using the tools `turbo setup` installed when the repository is restored, copied or remounted on a different device at the same path, as a snapshot or volume restore, a container layer, or a copied disk does. Until now the setup record stopped matching after such a restore, and every run exited 1 with `no matching trusted setup record`, even with the tool store and `turbo.lock` intact. - A setup record binds the canonical path, the inode, the `turbo.lock` digest, and the environment. The device id is still written, for diagnostics, but no longer compared. - Windows treats it the same way: the file index still binds the directory, the volume serial number no longer does. - Still rejected: a re-clone at the same path (new inode), a changed `turbo.lock`, a different canonical path, and a different environment. - Security trade, stated plainly: a different filesystem mounted at the same path, whose repo root has the same inode number and the same `turbo.lock`, is now trusted. The device id was the only thing that rejected that. - The record format does not change (schema 2, same fields), so existing records still parse, and an older binary still reads a new record and still requires the device to match. Validation: - `cargo fmt --check`; `cargo clippy -p turborepo-cli --all-targets -- -D warnings`; the trust and authorization tests (21 passed); `cargo test -p turbo --test setup` (37 passed). - New tests: a directory restored on another device is accepted by the strict consumer and by the shell hook; a new inode, a changed `turbo.lock`, and a different root are still rejected. - Loop-mounted ext4 images, a stand-in for a volume restore: install on device 1792, then mount the copy alone on 1793 at the same path with inode 12. `origin/main` rejects `turbo exec` and `turbo run`; this branch runs both. A copy of the directory at the same path (new inode) and an edited `turbo.lock` are still rejected. <details><summary>Before and after</summary> ### Identity Before: a record's directory identity was the pair `(st_dev, st_ino)` on Unix and `(dwVolumeSerialNumber, file index)` on Windows, compared for equality on every read of the record. After: only `st_ino` (the file index on Windows) is compared. `st_dev` (the volume serial number) is still recorded, unchanged, as a diagnostic: it is what changes when a filesystem is restored, copied or remounted on another device, so keeping it in the record leaves that visible without trusting it. ### The record Unchanged, byte for byte, for a given directory: schema 2, `{schema, root, device, inode, lock_sha256, env}`, plus `selection` for a `--no-lock` clone. From the repro after the restore: ```json {"schema":2,"root":"/mnt/work/repo","device":1792,"inode":12,"lock_sha256":"544dd825f4a1b0e21192ea51731f47a9ef6933223c38d5055e6ae8ad69763bc2","env":"38f07e7cdb09eb1f5fbee72d8fc2640fa9187de3da9b34e0417244a46258235c"} ``` `st_dev` is 1793 here, and the record still says 1792. Backward compatibility: new code reads a record written by a released canary (`2.11.11-canary.2`) as written, because the fields are unchanged and `deny_unknown_fields` is still satisfied. A record this code writes is readable by that binary too, which then enforces the device itself, so a downgrade is stricter, never looser. No schema bump is needed. ### Flow Before and after: `turbo setup` installs tools, then `TrustStore::write_record` reads `identity(root)` and writes the record. `turbo run` and `turbo exec` call `Authorization::inspect`/`authorize`, which capture the lock plan and call `TrustStore::authorize`; the shell hook calls `TrustStore::recorded`; both call `same_directory`, then `store.activate`. Only `same_directory` changed: it compares `record.inode` instead of `(record.device, record.inode)`. ### Receipts Install on the first mount, device 1792, inode 12: ``` $ stat -c "%d %i %n" /mnt/work /mnt/work/repo /mnt/work/.turbo-store 1792 2 /mnt/work 1792 12 /mnt/work/repo 1792 8193 /mnt/work/.turbo-store ``` The image copied while its loop device stays attached, and the copy mounted alone at the same path: ``` $ stat -c "%d %i %n" /mnt/work /mnt/work/repo /mnt/work/.turbo-store 1793 2 /mnt/work 1793 12 /mnt/work/repo 1793 8193 /mnt/work/.turbo-store ``` `origin/main`: ``` $ turbo exec -- node --version x managed setup validation failed: managed tools have no matching trusted | setup record; run turbo setup to repair the installation (turbo.lock is | unchanged) EXIT=1 $ turbo run build x managed setup validation failed: managed tools have no matching trusted | setup record; ... EXIT=1 ``` This branch: ``` $ turbo exec -- node --version v24.0.0 EXIT=0 $ turbo run build Tasks: 1 successful, 1 total EXIT=0 ``` Rejections on this branch, same restored mount. A copy of the directory at the same path (`mv repo repo.old && cp -a repo.old repo`, inode 8194 instead of 12) is rejected. Restoring the original inode with `mv repo.old repo` is accepted again, and appending a newline to `turbo.lock` (digest `04db87e9…` instead of `544dd825…`) is rejected for both `exec` and `run`. Logs: `/tmp/vts-trust/{install-main.log,stat-before.txt,stat-after.txt,before-restore.log,after-restore.log,reject-inode.log,reject-lock.log}`. </details> --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Author
Parents
Loading