From 4bad2a47cbf7ef241819477f8d24e37e580c0e86 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:18:24 +0000 Subject: [PATCH 1/2] fix(nix): honor NIXPKGS_ALLOW_UNFREE/INSECURE in print-dev-env devbox shell computes its environment with `nix print-dev-env`, which runs in pure mode by default and therefore ignores the NIXPKGS_ALLOW_UNFREE and NIXPKGS_ALLOW_INSECURE environment variables. As a result, unfree (or insecure) flake inputs fail to evaluate inside a devbox shell even when the user sets NIXPKGS_ALLOW_UNFREE=1, while building and installing the same packages works because those code paths already pass --impure. Run print-dev-env with --impure (and the corresponding allow-unfree / allow-insecure env) when the user has opted in via those environment variables. Pure mode is kept otherwise so Nix evaluation caching still applies for the common case. The existing IMPURE_PRINT_DEV_ENV feature flag continues to force impure mode unconditionally. Fixes #2196 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01KJZ8U5cYG5xLc4caCHt9jg --- internal/nix/eval.go | 7 ++++++ internal/nix/nix.go | 17 ++++++++++++++- internal/nix/nix_test.go | 46 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/internal/nix/eval.go b/internal/nix/eval.go index 81b827b833b..ffcf3da8e35 100644 --- a/internal/nix/eval.go +++ b/internal/nix/eval.go @@ -59,3 +59,10 @@ func IsInsecureAllowed() bool { allowed, _ := strconv.ParseBool(os.Getenv("NIXPKGS_ALLOW_INSECURE")) return allowed } + +// IsUnfreeAllowed reports whether the user has opted into unfree packages by +// setting the NIXPKGS_ALLOW_UNFREE environment variable to a truthy value. +func IsUnfreeAllowed() bool { + allowed, _ := strconv.ParseBool(os.Getenv("NIXPKGS_ALLOW_UNFREE")) + return allowed +} diff --git a/internal/nix/nix.go b/internal/nix/nix.go index c25444adb4e..be13de5d9b6 100644 --- a/internal/nix/nix.go +++ b/internal/nix/nix.go @@ -76,8 +76,9 @@ func (*NixInstance) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*Pr if len(data) == 0 { cmd := Command("print-dev-env", "--json") - if featureflag.ImpurePrintDevEnv.Enabled() { + if usePrintDevEnvImpure() { cmd.Args = append(cmd.Args, "--impure") + cmd.Env = allowUnfreeEnv(allowInsecureEnv(os.Environ())) } cmd.Args = append(cmd.Args, ref) slog.Debug("running print-dev-env cmd", "cmd", cmd) @@ -100,6 +101,20 @@ func (*NixInstance) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*Pr return &out, nil } +// usePrintDevEnvImpure reports whether `nix print-dev-env` should be run with +// the --impure flag. By default print-dev-env runs in pure mode, which ignores +// the NIXPKGS_ALLOW_UNFREE and NIXPKGS_ALLOW_INSECURE environment variables. +// When the user has opted into unfree or insecure packages, we run with +// --impure so those variables take effect, matching what devbox already does +// when building and installing packages. Pure mode is kept otherwise because +// --impure disables Nix's evaluation caching, which makes the command slower. +// See https://github.com/jetify-com/devbox/issues/2196. +func usePrintDevEnvImpure() bool { + return featureflag.ImpurePrintDevEnv.Enabled() || + IsUnfreeAllowed() || + IsInsecureAllowed() +} + func savePrintDevEnvCache(path string, out PrintDevEnvOut) error { data, err := json.Marshal(out) if err != nil { diff --git a/internal/nix/nix_test.go b/internal/nix/nix_test.go index f43c405346d..9924b884152 100644 --- a/internal/nix/nix_test.go +++ b/internal/nix/nix_test.go @@ -4,6 +4,52 @@ import ( "testing" ) +func TestIsUnfreeAllowed(t *testing.T) { + cases := map[string]bool{ + "": false, + "0": false, + "false": false, + "1": true, + "true": true, + } + for value, want := range cases { + t.Run(value, func(t *testing.T) { + t.Setenv("NIXPKGS_ALLOW_UNFREE", value) + if got := IsUnfreeAllowed(); got != want { + t.Errorf("IsUnfreeAllowed() with NIXPKGS_ALLOW_UNFREE=%q = %v, want %v", value, got, want) + } + }) + } +} + +// TestUsePrintDevEnvImpure verifies the fix for +// https://github.com/jetify-com/devbox/issues/2196: print-dev-env must run +// with --impure whenever the user opts into unfree or insecure packages so +// those environment variables are honored. +func TestUsePrintDevEnvImpure(t *testing.T) { + cases := []struct { + name string + unfree string + insec string + want bool + }{ + {name: "neither set", want: false}, + {name: "unfree disabled", unfree: "0", want: false}, + {name: "unfree allowed", unfree: "1", want: true}, + {name: "insecure allowed", insec: "true", want: true}, + {name: "both allowed", unfree: "1", insec: "1", want: true}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("NIXPKGS_ALLOW_UNFREE", tt.unfree) + t.Setenv("NIXPKGS_ALLOW_INSECURE", tt.insec) + if got := usePrintDevEnvImpure(); got != tt.want { + t.Errorf("usePrintDevEnvImpure() = %v, want %v", got, tt.want) + } + }) + } +} + func TestParseInsecurePackagesFromExitError(t *testing.T) { errorText := ` at /nix/store/xwl0am98klc8mz074jdyvpnyc6vwzlla-source/lib/customisation.nix:267:17: From 3ac083d60b699527869b165ec9a4c3849439d9a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:27:40 +0000 Subject: [PATCH 2/2] fix(nix): address review feedback on print-dev-env impure opt-in - Only inject the allow-unfree / allow-insecure variables the user actually opted into, instead of forcing both. When impure mode is enabled solely via the IMPURE_PRINT_DEV_ENV feature flag, the child environment is left inheriting the parent unchanged. - Also honor NIXPKGS_ALLOW_UNFREE / NIXPKGS_ALLOW_INSECURE set in devbox.json's "env" block, not just the process environment. These are resolved in execPrintDevEnv (before config env is otherwise merged) and threaded through PrintDevEnvArgs. - Make the impure-decision unit test hermetic with respect to the IMPURE_PRINT_DEV_ENV feature flag, and cover the feature-flag path. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01KJZ8U5cYG5xLc4caCHt9jg --- internal/devbox/devbox.go | 9 +++++++++ internal/nix/nix.go | 34 ++++++++++++++++++++++++++++------ internal/nix/nix_test.go | 36 +++++++++++++++++++++++------------- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/internal/devbox/devbox.go b/internal/devbox/devbox.go index 8ac001233d3..e68fbb25329 100644 --- a/internal/devbox/devbox.go +++ b/internal/devbox/devbox.go @@ -629,10 +629,19 @@ func (d *Devbox) execPrintDevEnv(ctx context.Context, usePrintDevEnvCache bool) spinny.Start() } + // Allow unfree/insecure packages to evaluate when the user opts in via + // devbox.json's "env" (the process environment is handled separately + // inside PrintDevEnv). See https://github.com/jetify-com/devbox/issues/2196. + configEnv := d.cfg.Env() + allowUnfree, _ := strconv.ParseBool(configEnv["NIXPKGS_ALLOW_UNFREE"]) + allowInsecure, _ := strconv.ParseBool(configEnv["NIXPKGS_ALLOW_INSECURE"]) + vaf, err := d.nix.PrintDevEnv(ctx, &nix.PrintDevEnvArgs{ FlakeDir: d.flakeDir(), PrintDevEnvCachePath: d.nixPrintDevEnvCachePath(), UsePrintDevEnvCache: usePrintDevEnvCache, + AllowUnfree: allowUnfree, + AllowInsecure: allowInsecure, }) if spinny != nil { spinny.Stop() diff --git a/internal/nix/nix.go b/internal/nix/nix.go index be13de5d9b6..2fa330e8a4e 100644 --- a/internal/nix/nix.go +++ b/internal/nix/nix.go @@ -45,6 +45,13 @@ type PrintDevEnvArgs struct { FlakeDir string PrintDevEnvCachePath string UsePrintDevEnvCache bool + + // AllowUnfree and AllowInsecure report whether the user has opted into + // unfree or insecure packages through the resolved devbox config (for + // example via NIXPKGS_ALLOW_UNFREE in devbox.json's "env"). They are in + // addition to the same variables read from the process environment. + AllowUnfree bool + AllowInsecure bool } // PrintDevEnv calls `nix print-dev-env -f ` and returns its output. The output contains @@ -75,10 +82,27 @@ func (*NixInstance) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*Pr ref := flake.Ref{Type: flake.TypePath, Path: flakeDirResolved} if len(data) == 0 { + // Honor unfree/insecure opt-in from either the process environment + // or the resolved devbox config. + allowUnfree := args.AllowUnfree || IsUnfreeAllowed() + allowInsecure := args.AllowInsecure || IsInsecureAllowed() + cmd := Command("print-dev-env", "--json") - if usePrintDevEnvImpure() { + if usePrintDevEnvImpure(allowUnfree, allowInsecure) { cmd.Args = append(cmd.Args, "--impure") - cmd.Env = allowUnfreeEnv(allowInsecureEnv(os.Environ())) + // Only inject the allow-* variables the user actually opted + // into. When impure mode is enabled solely via the feature + // flag, cmd.Env is left inheriting the parent environment. + if allowUnfree || allowInsecure { + env := os.Environ() + if allowUnfree { + env = allowUnfreeEnv(env) + } + if allowInsecure { + env = allowInsecureEnv(env) + } + cmd.Env = env + } } cmd.Args = append(cmd.Args, ref) slog.Debug("running print-dev-env cmd", "cmd", cmd) @@ -109,10 +133,8 @@ func (*NixInstance) PrintDevEnv(ctx context.Context, args *PrintDevEnvArgs) (*Pr // when building and installing packages. Pure mode is kept otherwise because // --impure disables Nix's evaluation caching, which makes the command slower. // See https://github.com/jetify-com/devbox/issues/2196. -func usePrintDevEnvImpure() bool { - return featureflag.ImpurePrintDevEnv.Enabled() || - IsUnfreeAllowed() || - IsInsecureAllowed() +func usePrintDevEnvImpure(allowUnfree, allowInsecure bool) bool { + return featureflag.ImpurePrintDevEnv.Enabled() || allowUnfree || allowInsecure } func savePrintDevEnvCache(path string, out PrintDevEnvOut) error { diff --git a/internal/nix/nix_test.go b/internal/nix/nix_test.go index 9924b884152..8eb8328c8a1 100644 --- a/internal/nix/nix_test.go +++ b/internal/nix/nix_test.go @@ -28,28 +28,38 @@ func TestIsUnfreeAllowed(t *testing.T) { // those environment variables are honored. func TestUsePrintDevEnvImpure(t *testing.T) { cases := []struct { - name string - unfree string - insec string - want bool + name string + allowUnfree bool + allowInsecure bool + want bool }{ - {name: "neither set", want: false}, - {name: "unfree disabled", unfree: "0", want: false}, - {name: "unfree allowed", unfree: "1", want: true}, - {name: "insecure allowed", insec: "true", want: true}, - {name: "both allowed", unfree: "1", insec: "1", want: true}, + {name: "neither opted in", want: false}, + {name: "unfree opted in", allowUnfree: true, want: true}, + {name: "insecure opted in", allowInsecure: true, want: true}, + {name: "both opted in", allowUnfree: true, allowInsecure: true, want: true}, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - t.Setenv("NIXPKGS_ALLOW_UNFREE", tt.unfree) - t.Setenv("NIXPKGS_ALLOW_INSECURE", tt.insec) - if got := usePrintDevEnvImpure(); got != tt.want { - t.Errorf("usePrintDevEnvImpure() = %v, want %v", got, tt.want) + // Keep the result independent of any ambient feature-flag env. + t.Setenv("DEVBOX_FEATURE_IMPURE_PRINT_DEV_ENV", "0") + if got := usePrintDevEnvImpure(tt.allowUnfree, tt.allowInsecure); got != tt.want { + t.Errorf("usePrintDevEnvImpure(%v, %v) = %v, want %v", + tt.allowUnfree, tt.allowInsecure, got, tt.want) } }) } } +// TestUsePrintDevEnvImpureFeatureFlag verifies that the IMPURE_PRINT_DEV_ENV +// feature flag forces impure mode even when the user hasn't opted into unfree +// or insecure packages. +func TestUsePrintDevEnvImpureFeatureFlag(t *testing.T) { + t.Setenv("DEVBOX_FEATURE_IMPURE_PRINT_DEV_ENV", "1") + if !usePrintDevEnvImpure(false, false) { + t.Error("usePrintDevEnvImpure(false, false) = false with feature flag set, want true") + } +} + func TestParseInsecurePackagesFromExitError(t *testing.T) { errorText := ` at /nix/store/xwl0am98klc8mz074jdyvpnyc6vwzlla-source/lib/customisation.nix:267:17: