Symlink to path coercion

Nix copies symlinks directly into the store, e.g. ln -s /etc sym then “${sym}” will make a symlink to /etc in the store. If you build with sandbox then it’ll be a broken link, but without sandbox it’ll just walk into /etc. Is this intentional? I can’t think of a good reason to do this, vs say refusing to store symlinks or resolving them to their destinations, but it’s clearly done that way on purpose. I looked about in the source but couldn’t find any comments about why. The closest I got was that copyPathToStore calls fetchToStore with path.resolveSymlinks() which implies they are meant to be resolved, at least to either Ancestors or Full, but they’re not.

The risk of course is that people will feed srcs into a derivation with path references, as they are supposed to, but someone will get clever and use a symlink. If it’s a relative link, it’ll turn into a broken link and they’ll get a confusing “file not found”, and if it’s an absolute link, it will either do the file not found thing if in a sandbox, or worse, if not in a sandbox, work on the local filesystem but not actually be reproducible on another one.

For me the simplest would be to simply refuse to store symlinks. A fancier way would be to store them if say you store a directory, and the symlink is relative and doesn’t escape the directory, similar to rsync’s notion of “safe” links. They could also get automatically flattened, but that would be surprising in a world where the standard is to not do that unless you pass a --follow-links type flag.

My understanding is that in ancient times this was intentional to make software build unmodified. Forbidding symlinks, which sounds like the sane thing to do when living in a disciplined, nixified world, would probably have broken a lot of stuff. Traversing symlinks is needed because profiles are symlink trees.

Filtering for relative symlinks to subpaths does sound reasonable, but there may still be a risk of breaking some assumptions in Nixpkgs. I’d support a change that tests enough of history to prove it’s a safe additional constraint to make.

1 Like

I can imagine where there will be some src directory which uses symlinks inside, and so of course we should preserve them since the build script will use them. The case for symlinks pointing outside is weaker but they could be necessary for unsandboxed builds. That’s not exactly ideal, but unsandboxing is a non-ideal practical necessity anyway.

I have a nix linter at work that can parse enough nix to make assertions about things, maybe the best way is simply to locally lint that any file references cannot point to symlinks, or directories containing symlinks.

Thanks for the context!

1 Like