Description
The deploy tree's ownership reset reaches outside the deploy tree. pnpm stores
package files once and hardlinks them into every node_modules that needs them,
so chown -R root:overflow /srv/overflow and chmod -R u=rwX,g=rX,o= /srv/overflow
change the ownership and mode of the shared inode, not of a copy. Every other
checkout on the host, and the store itself, see the change.
Observed on the deployment host today. A store file picked at random is owned by
root:overflow with mode 0640, has 12 links, and is the same inode as files in
three separate checkouts including /srv/overflow:
/root/.local/share/pnpm/store/v10/files/27/98c261d336da18f5d5ff826c0a393c60baeb219d2b16b444614c8ad36fb013ed107601259f53c7091de928834f335848dcfd59d7e7234e154814ae652bb81e
inode=920054 links=12 root:overflow -rw-r-----
$ find /srv/overflow/node_modules /root/overflow/node_modules /tmp/<a worktree>/node_modules -samefile <that store file>
/tmp/<a worktree>/node_modules/.pnpm/next-auth@5.0.0-beta.32_.../node_modules/next-auth/src/providers/webauthn.ts
/root/overflow/node_modules/.pnpm/next-auth@5.0.0-beta.32_.../node_modules/next-auth/src/providers/webauthn.ts
/srv/overflow/node_modules/.pnpm/next-auth@5.0.0-beta.32_.../node_modules/next-auth/src/providers/webauthn.ts
root:overflow with o= is what deploy/README.md prescribes for /srv/overflow
and is correct there — the service account has to read the code it runs. It is not
correct for the store, and it is not correct for an unrelated project that installs
from the same store. 29,294 paths under /root/overflow alone currently carry it.
The effect is currently benign: everything involved lives under /root, which is
mode 0700, so the overflow account cannot traverse to any of it. What is not
benign is that the state cannot simply be repaired — restoring the store to
root:root would strip the group read that /srv/overflow depends on, through the
same shared inodes, and the service would fail to load its dependencies on the next
restart.
Expected Behavior
Deploying changes ownership and permissions only inside the deployment tree. Files
belonging to the package store, and to checkouts that are not being deployed, keep
the ownership and modes they had.
Reproduction Steps
-
On the deployment host, pick any file under the pnpm store that is not owned by
root:root:
find /root/.local/share/pnpm/store/v10/files -not -group root -type f | head -1
-
Read its link count, owner and mode:
stat -c '%n inode=%i links=%h %U:%G %A' <that file>
Observed: links=12 root:overflow -rw-r-----.
-
Find the other names for that same inode:
find /srv/overflow/node_modules /root/overflow/node_modules -samefile <that file>
Observed: one path in each checkout, confirming /srv/overflow and unrelated
checkouts share the inode with the store.
-
Count how far it has spread in a checkout that is not the deployment tree:
find /root/overflow -not -group root | wc -l
Observed: 30,954, of which 29,294 are under node_modules.
Environment / Context
Deployment host vmi3458323; /srv/overflow deployed per deploy/README.md
section 10, which runs chown -R root:overflow /srv/overflow followed by
chmod -R u=rwX,g=rX,o= /srv/overflow. pnpm 10.33.0, default store at
/root/.local/share/pnpm/store/v10, content-addressable with hardlinks. Node
24.17.0. The service account is overflow:overflow (999:989).
The ownership of /srv/overflow itself is deliberate and this report does not
propose changing it.
Discovered During
Pull request #205 (issue #195), while an agent executing the deploy procedure on a
scratch tree disclosed that its own recursive ownership commands had reached
hardlinked git objects in a protected checkout. Investigating that led to the
store-wide state described above, which predates the pull request and is a property
of the existing procedure rather than of any change in it.
Suggested Fix
Unverified. Two directions, either of which would need testing before adoption:
break the store's hardlinks for the deployment tree (pnpm install supports copying
instead of linking, via package-import-method=copy, at the cost of disk and
install time), or give the deployment tree its own store so the shared inodes are
never the production ones. A narrower chown/chmod that skips node_modules
would not work on its own, because the service must be able to read it.
Description
The deploy tree's ownership reset reaches outside the deploy tree.
pnpmstorespackage files once and hardlinks them into every
node_modulesthat needs them,so
chown -R root:overflow /srv/overflowandchmod -R u=rwX,g=rX,o= /srv/overflowchange the ownership and mode of the shared inode, not of a copy. Every other
checkout on the host, and the store itself, see the change.
Observed on the deployment host today. A store file picked at random is owned by
root:overflowwith mode0640, has 12 links, and is the same inode as files inthree separate checkouts including
/srv/overflow:root:overflowwitho=is whatdeploy/README.mdprescribes for/srv/overflowand is correct there — the service account has to read the code it runs. It is not
correct for the store, and it is not correct for an unrelated project that installs
from the same store. 29,294 paths under
/root/overflowalone currently carry it.The effect is currently benign: everything involved lives under
/root, which ismode
0700, so theoverflowaccount cannot traverse to any of it. What is notbenign is that the state cannot simply be repaired — restoring the store to
root:rootwould strip the group read that/srv/overflowdepends on, through thesame shared inodes, and the service would fail to load its dependencies on the next
restart.
Expected Behavior
Deploying changes ownership and permissions only inside the deployment tree. Files
belonging to the package store, and to checkouts that are not being deployed, keep
the ownership and modes they had.
Reproduction Steps
On the deployment host, pick any file under the pnpm store that is not owned by
root:root:find /root/.local/share/pnpm/store/v10/files -not -group root -type f | head -1Read its link count, owner and mode:
Observed:
links=12 root:overflow -rw-r-----.Find the other names for that same inode:
Observed: one path in each checkout, confirming
/srv/overflowand unrelatedcheckouts share the inode with the store.
Count how far it has spread in a checkout that is not the deployment tree:
find /root/overflow -not -group root | wc -lObserved: 30,954, of which 29,294 are under
node_modules.Environment / Context
Deployment host
vmi3458323;/srv/overflowdeployed perdeploy/README.mdsection 10, which runs
chown -R root:overflow /srv/overflowfollowed bychmod -R u=rwX,g=rX,o= /srv/overflow. pnpm 10.33.0, default store at/root/.local/share/pnpm/store/v10, content-addressable with hardlinks. Node24.17.0. The service account is
overflow:overflow(999:989).The ownership of
/srv/overflowitself is deliberate and this report does notpropose changing it.
Discovered During
Pull request #205 (issue #195), while an agent executing the deploy procedure on a
scratch tree disclosed that its own recursive ownership commands had reached
hardlinked git objects in a protected checkout. Investigating that led to the
store-wide state described above, which predates the pull request and is a property
of the existing procedure rather than of any change in it.
Suggested Fix
Unverified. Two directions, either of which would need testing before adoption:
break the store's hardlinks for the deployment tree (
pnpm installsupports copyinginstead of linking, via
package-import-method=copy, at the cost of disk andinstall time), or give the deployment tree its own store so the shared inodes are
never the production ones. A narrower
chown/chmodthat skipsnode_moduleswould not work on its own, because the service must be able to read it.