Skip to content

Deploying rewrites ownership and permissions of files shared with other checkouts through the pnpm store #214

Description

@Nitjsefnie

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

  1. 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
  2. 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-----.

  3. 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.

  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: infraDeployment, database, migrations and opsbugSomething isn't workingoffered: mediumOpening catalog · comparison 5 · reserve 5

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions