Skip to content

nixos: the checkout unit runs the least trusted code with no systemd hardening #50

Open

Description

@euoia

Follow-up from #48.

The hardening attrset in nixos/astroadmin-instance.nix — NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, ProtectKernelTunables, ProtectKernelModules, ProtectControlGroups, RestrictSUIDSGID, LockPersonality — is applied only to the two long-running units. Its own comment says it is "systemd hardening shared by the long-running units".

The checkout one-shot gets none of it, and that unit is where bun install runs: arbitrary postinstall scripts from the site's dependency tree, i.e. the least trusted code on the host. The servers it feeds are sandboxed; the thing that installs them is not.

#48 adds RestrictNamespaces = true to it directly, because that is the one that closes the kernel-privesc vector and it was tested against a real install. The rest was left alone deliberately — the shared set has never been validated against a package install, and quietly applying ProtectSystem=strict to something that writes node_modules is how you get a confusing failure at 2am.

What needs checking

Most of it looks like it should just work — the unit already declares StateDirectory, so the writes it needs are inside a path systemd grants, and HOME is the state dir rather than a real home, so ProtectHome should be inert. But "looks like" is not a test.

  • Does bun install --frozen-lockfile complete under ProtectSystem=strict for a site with native/postinstall dependencies, not just a clean one?
  • Does PrivateTmp break any package that shells out through /tmp?
  • Does the initial git clone path still work (different from the fast-forward path, and only exercised on a fresh instance)?

Test it the way #48 was tested: /run/systemd/system drop-ins on a live instance, exercise the real thing, then remove them so the box matches its declared config again. Confirm the property actually applied with systemctl show -p <Property> before trusting the result — a first attempt at #48 wrote drop-ins to /etc/systemd/system, which is read-only on NixOS, and it would have read as a clean pass if the property had not been checked.

Activity

  1. euoia commented on Oct 3, 2026

    @euoia
    ContributorAuthor

    Update after #48's review: one item from the shared set already applies here. With User= set, systemd implies NoNewPrivileges=yes for any seccomp-based option, so RestrictNamespaces = true on the checkout unit turned it on too. The live bun install run in #48 passed with it in place, so that one is covered. The rest of the list above still needs testing.

  2. euoia commented on Oct 12, 2026

    @euoia
    ContributorAuthor

    Related, and broader than the checkout unit: no editor unit has resource or network limits. On main, git grep -E "MemoryMax|CPUQuota|TasksMax|IPAddressDeny|ProtectProc" -- nixos returns nothing.

    When this is picked up, consider adding these to the admin, preview and checkout units together:

    • MemoryMax, CPUQuota and TasksMax, so one site can't OOM or fork-bomb the box;
    • IPAddressDeny=localhost plus an allow for the unit's own port, so sibling editors' loopback ports are unreachable (see astroadmin-ops#3);
    • ProtectProc=invisible, PrivateDevices, and a SystemCallFilter=@system-service baseline.
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions