Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions internal/devbox/devbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,15 @@ func (d *Devbox) computeEnv(
for k, v := range nixEnv {
env[k] = v
}

// The Nix dev-env sets the SSL certificate-bundle variables to a Nix
// store path whenever a package pulls in nss-cacert (e.g. httpie,
// python). That clobbers a value the user deliberately set in their own
// environment — most importantly a corporate MITM/proxy CA bundle —
// breaking outbound TLS for the rest of the project. Restore the user's
// own value so custom CA bundles keep working, mirroring how `nix shell`
// leaves these untouched. See jetify-com/devbox#2604.
preserveUserSSLCertFiles(env, originalEnv)
}
slog.Debug("nix environment PATH", "path", env["PATH"])

Expand Down Expand Up @@ -1100,6 +1109,27 @@ var ignoreDevEnvVar = map[string]bool{
"UID": true,
}

// sslCertFileEnvVars are the certificate-bundle environment variables that
// Nix's build environment sets (via packages such as nss-cacert, pulled in by
// httpie, python, etc.). They point at a Nix store CA bundle, which is the right
// default when the user hasn't set one themselves but is wrong when they have:
// a user who exports one of these — typically to a corporate MITM/proxy CA
// bundle — needs that value preserved for outbound TLS to keep working.
var sslCertFileEnvVars = []string{"NIX_SSL_CERT_FILE", "SSL_CERT_FILE"}

// preserveUserSSLCertFiles restores the user's own certificate-bundle variables
// (taken from userEnv, the ambient environment captured before the Nix dev-env
// is layered on) into env, so that a value the user explicitly set wins over the
// Nix store path injected by the dev-env. Variables the user did not set are
// left as-is, keeping the Nix default. See jetify-com/devbox#2604.
func preserveUserSSLCertFiles(env, userEnv map[string]string) {
for _, key := range sslCertFileEnvVars {
if val, ok := userEnv[key]; ok && val != "" {
env[key] = val
}
}
}

func (d *Devbox) ProjectDirHash() string {
return cachehash.Bytes([]byte(d.projectDir))
}
Expand Down
62 changes: 62 additions & 0 deletions internal/devbox/devbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,68 @@ func TestComputeDevboxPathWhenRemoving(t *testing.T) {
assert.NotEqual(t, path, path2, "path should not be the same")
}

// testNixVars is a nix.Nixer mock whose PrintDevEnv returns a configurable set
// of exported variables. Used to exercise how computeEnv layers the Nix
// dev-env on top of the ambient environment.
type testNixVars struct {
vars map[string]string
}

func (n *testNixVars) PrintDevEnv(ctx context.Context, args *nix.PrintDevEnvArgs) (*nix.PrintDevEnvOut, error) {
variables := map[string]nix.Variable{}
for k, v := range n.vars {
variables[k] = nix.Variable{Type: "exported", Value: v}
}
return &nix.PrintDevEnvOut{Variables: variables}, nil
}

func TestPreserveUserSSLCertFiles(t *testing.T) {
const userBundle = "/Library/Application Support/Netskope/STAgent/data/nscacert_combined.pem"
const nixBundle = "/nix/store/abc-nss-cacert-3.108/etc/ssl/certs/ca-bundle.crt"

t.Run("restores user value when set", func(t *testing.T) {
env := map[string]string{"NIX_SSL_CERT_FILE": nixBundle, "SSL_CERT_FILE": nixBundle}
userEnv := map[string]string{"NIX_SSL_CERT_FILE": userBundle, "SSL_CERT_FILE": userBundle}
preserveUserSSLCertFiles(env, userEnv)
assert.Equal(t, userBundle, env["NIX_SSL_CERT_FILE"])
assert.Equal(t, userBundle, env["SSL_CERT_FILE"])
})

t.Run("keeps nix value when user did not set one", func(t *testing.T) {
env := map[string]string{"NIX_SSL_CERT_FILE": nixBundle}
preserveUserSSLCertFiles(env, map[string]string{})
assert.Equal(t, nixBundle, env["NIX_SSL_CERT_FILE"])
})

t.Run("ignores empty user value", func(t *testing.T) {
env := map[string]string{"NIX_SSL_CERT_FILE": nixBundle}
preserveUserSSLCertFiles(env, map[string]string{"NIX_SSL_CERT_FILE": ""})
assert.Equal(t, nixBundle, env["NIX_SSL_CERT_FILE"])
})
}

// TestComputeEnvPreservesUserSSLCertFile is a regression test for
// jetify-com/devbox#2604: adding a package that pulls in nss-cacert (e.g.
// httpie) must not clobber a NIX_SSL_CERT_FILE the user set in their own
// environment (e.g. a corporate MITM CA bundle).
func TestComputeEnvPreservesUserSSLCertFile(t *testing.T) {
const userBundle = "/Library/Application Support/Netskope/STAgent/data/nscacert_combined.pem"
const nixBundle = "/nix/store/abc-nss-cacert-3.108/etc/ssl/certs/ca-bundle.crt"

d := devboxForTesting(t)
d.nix = &testNixVars{vars: map[string]string{
"PATH": "/tmp/my/path",
"NIX_SSL_CERT_FILE": nixBundle,
}}

t.Setenv("NIX_SSL_CERT_FILE", userBundle)

env, err := d.computeEnv(t.Context(), false /*use cache*/, devopt.EnvOptions{})
require.NoError(t, err, "computeEnv should not fail")
assert.Equal(t, userBundle, env["NIX_SSL_CERT_FILE"],
"the user's NIX_SSL_CERT_FILE should win over the nix dev-env value")
}

func devboxForTesting(t *testing.T) *Devbox {
path := t.TempDir()
_, err := devconfig.Init(path)
Expand Down
Loading