Is it possible to prevent configuration.nix from being read by unprivileged users?

For security purposes I’ve recursively set permissions of /etc/nixos to 700, but I realized that the configuration gets symlinked in multiple places.

/run/current-system/configuration.nix for one, when system.copySystemConfiguration = true;. I’ve disabled it, but it remains in /run/booted-system as well.

Then there are many in the Nix store, which is world-readable.

I couldn’t find discussion around hiding entire system configurations, only atomic secrets using sops.

Is this feasible?

So, basically security through obscurity? What setting would be an example of something you want hidden?

It’s more so for privacy, I should’ve mentioned. For hobby I have a small hosting shop with several users on it for which I create docker containers & systemd user services declaratively. I’ve set hidepid=2 to limit the visibility of processes to only of those they own, but the user’s ability to read nix files defeats its purpose to a degree.

Furthermore, currently I share with friends and split server costs but at some point I wish to let on strangers whom I don’t want to copy my server config.

As it stands I can only think of virtualization, but I’m quite happy with my bare metal setup right now.

There isn’t really much of a point to this as the config’s effects are encoded in the current generation which is also world-readable.

That’s just due to how /run/booted-system works: It will, by design, not change until you reboot.

Those are just part of the previous generations. They will be garbage-collected when the gcroots of the old generations are deleted and you run a gc.

1 Like

Technically yes, practically, very difficult if not impossible. You would have to:

  1. mount /nix/store without the executable read bit, making it essentially content-addressed.

  2. hide all references to /nix/store paths.

Point 2. means hiding any reference to the system (/run/booted-system, /run/current-system, etc.), hiding process by mounting /proc with hidepid (good luck with this as it breaks systemd), every symlink in /etc/static

I’m sure someone with enough time will eventually find a way to get a handle to /run/current-system that you missed. So… not really feasible.

1 Like

Ok, thanks for clarifying.

Here’s a previous discussion about the idea of hiding /nix/store from users:

2 Likes

Note that that RFC can only address purity concerns of applications loading stuff from wrong parts of the nix store, and not privacy or even security concerns. This is because due to the nature of the cache, it has a big timing side channel.

2 Likes

Here’s a patch which adds a new option to disable store enumeration in the daemon:

Patch
From 4c26abd5762bbeb0c952d8ffdb1ad3f1dd1c02f9 Mon Sep 17 00:00:00 2001
From: Claude Code <noreply@anthropic.com>
Date: Sun, 15 Feb 2026 16:06:56 +0000
Subject: [PATCH 1/2] daemon: restrict CollectGarbage to trusted users

Garbage collection was previously allowed for all connected users.
The results include the set of deleted paths, which leaks information
about store contents. Restrict this operation to trusted users only,
consistent with other privileged operations like AddPermRoot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 src/libstore/daemon.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 155fe2432..4f32e80f6 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -743,6 +743,8 @@ static void performOp(
         GCResults results;
 
         logger->startWork();
+        if (!trusted)
+            throw Error("you are not privileged to collect garbage");
         if (options.ignoreLiveness)
             throw Error("you are not allowed to ignore liveness");
         auto & gcStore = require<GcStore>(*store);
-- 
2.49.0

From f5e8e67d8b36f62e59dd535b0472700736809c21 Mon Sep 17 00:00:00 2001
From: Claude Code <noreply@anthropic.com>
Date: Sun, 15 Feb 2026 16:11:51 +0000
Subject: [PATCH 2/2] daemon: add unlistable-store setting to prevent store
 enumeration

Add a new `unlistable-store` boolean setting (default: false). When
enabled, untrusted users are denied operations that reveal store paths
they do not already know:

- QueryAllValidPaths: dumps the entire store inventory
- QueryReferrers: reveals reverse dependencies (e.g. querying
  referrers of glibc enumerates most of the store)
- QueryValidDerivers: reveals derivation paths from output paths
- FindRoots: enumerates store paths referenced by all processes
  via /proc

"Oracle" operations that require the caller to already know the full
store path (IsValidPath, QueryValidPaths, QueryPathInfo, etc.) remain
permitted, since knowledge of a path implies the caller can compute it
from the derivation inputs.

Intended for multi-tenant systems with a shared Nix store. Combine
with `chmod o-r /nix/store` to also block filesystem-level listing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 src/libstore/daemon.cc | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index 4f32e80f6..07483da59 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -18,6 +18,7 @@
 #include "nix/util/git.hh"
 #include "nix/util/logging.hh"
 #include "nix/store/globals.hh"
+#include "nix/util/config-global.hh"
 
 #ifndef _WIN32 // TODO need graceful async exit support on Windows?
 #  include "nix/util/monitor-fd.hh"
@@ -27,6 +28,35 @@
 
 namespace nix::daemon {
 
+struct DaemonSettings : Config
+{
+    Setting<bool> unlistableStore{
+        this,
+        false,
+        "unlistable-store",
+        R"(
+          If set to `true`, users that are not in
+          [`trusted-users`](#conf-trusted-users) cannot enumerate store
+          contents.  Operations that list or discover store paths the
+          caller does not already know — such as querying all valid
+          paths, querying referrers, or finding GC roots — will be
+          denied.
+
+          This is intended for multi-tenant systems where a shared Nix
+          store is used and tenants should not be able to discover each
+          other's build outputs.  Combine with removing read permission
+          on the store directory (e.g. `chmod o-r /nix/store`) to also
+          prevent filesystem-level enumeration.
+
+          Note that "oracle" queries — checking whether a specific,
+          already-known store path is valid — are still permitted.
+        )"};
+};
+
+static DaemonSettings daemonSettings;
+
+static GlobalConfig::Register rDaemonSettings(&daemonSettings);
+
 Sink & operator<<(Sink & sink, const Logger::Fields & fields)
 {
     sink << fields.size();
@@ -365,6 +395,9 @@ static void performOp(
     case WorkerProto::Op::QueryDerivationOutputs: {
         auto path = WorkerProto::Serialise<StorePath>::read(*store, rconn);
         logger->startWork();
+        if (!trusted && daemonSettings.unlistableStore
+            && (op == WorkerProto::Op::QueryReferrers || op == WorkerProto::Op::QueryValidDerivers))
+            throw Error("you are not privileged to enumerate store paths");
         StorePathSet paths;
         if (op == WorkerProto::Op::QueryReferrers)
             store->queryReferrers(path, paths);
@@ -711,6 +744,8 @@ static void performOp(
 
     case WorkerProto::Op::FindRoots: {
         logger->startWork();
+        if (!trusted && daemonSettings.unlistableStore)
+            throw Error("you are not privileged to enumerate store paths");
         auto & gcStore = require<GcStore>(*store);
         Roots roots = gcStore.findRoots(!trusted);
         logger->stopWork();
@@ -833,6 +868,8 @@ static void performOp(
 
     case WorkerProto::Op::QueryAllValidPaths: {
         logger->startWork();
+        if (!trusted && daemonSettings.unlistableStore)
+            throw Error("you are not privileged to enumerate store paths");
         auto paths = store->queryAllValidPaths();
         logger->stopWork();
         WorkerProto::write(*store, wconn, paths);
-- 
2.49.0

All previously discussed caveats apply.

It also makes garbage collection a trusted operation (because the logging leaks store paths, but also because you probably don’t want semi-trusted tenants from triggering GC of the entire store anyway).

The patch is machine-generated; one take-away from it is that the straight-forward implementation is small and unintrusive, so implementation complexity is a non-issue.