diff --git a/pressure-vessel/environ.c b/pressure-vessel/environ.c
new file mode 100644
index 0000000000000000000000000000000000000000..4840839f28ccfc09205a61dfdf4d856e058c4915
--- /dev/null
+++ b/pressure-vessel/environ.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright © 2014-2018 Red Hat, Inc
+ * Copyright © 2020 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "environ.h"
+
+/*
+ * PvEnviron:
+ *
+ * Each environment variable we deal with in pressure-vessel has
+ * the following possible values:
+ *
+ * - Set to a value (empty or non-empty)
+ * - Forced to be unset
+ * - Inherited from the execution environment of bwrap(1)
+ *
+ * together with a *locked* flag. The locked flag indicates that if the
+ * container is running pressure-vessel-launcher(1), then the variable
+ * cannot be overridden by pressure-vessel-launch(1).
+ *
+ * In particular, the locked flag can be combined with any value, and
+ * a variable can also be locked to the inherited value.
+ *
+ * We represent this as follows:
+ *
+ * - Set to a value: values[VAR] = VAL
+ * - Forced to be unset: values[VAR] = NULL
+ * - Inherited from the execution environment of bwrap(1): VAR not in `values`
+ *
+ * Variables that are locked are also added to `locked`.
+ */
+struct _PvEnviron
+{
+  /* (element-type filename filename) */
+  GHashTable *values;
+  /* (element-type filename ignored) */
+  GHashTable *locked;
+};
+
+PvEnviron *
+pv_environ_new (void)
+{
+  g_autoptr(PvEnviron) self = g_slice_new0 (PvEnviron);
+
+  self->values = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                        g_free, g_free);
+  self->locked = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                        g_free, NULL);
+  return g_steal_pointer (&self);
+}
+
+void
+pv_environ_free (PvEnviron *self)
+{
+  g_return_if_fail (self != NULL);
+
+  g_clear_pointer (&self->values, g_hash_table_unref);
+  g_clear_pointer (&self->locked, g_hash_table_unref);
+  g_slice_free (PvEnviron, self);
+}
+
+/*
+ * Lock @var to be set to @val, which may be %NULL to unset it.
+ * It cannot be overridden by a subsequent pressure-vessel-launch
+ * invocation.
+ */
+void
+pv_environ_lock_env (PvEnviron *self,
+                     const char *var,
+                     const char *val)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (var != NULL);
+  /* val may be NULL, to lock it to be unset */
+
+  g_hash_table_replace (self->values, g_strdup (var), g_strdup (val));
+  g_hash_table_add (self->locked, g_strdup (var));
+}
+
+/*
+ * Lock @var to whatever value it happens to have inherited, in a way
+ * that cannot be overridden by a subsequent pressure-vessel-launch
+ * invocation.
+ */
+void
+pv_environ_lock_inherit_env (PvEnviron *self,
+                             const char *var)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (var != NULL);
+
+  g_hash_table_remove (self->values, var);
+  g_hash_table_add (self->locked, g_strdup (var));
+}
+
+/*
+ * Set @var to @val, in a way that can be overridden by a subsequent
+ * pressure-vessel-launch invocation.
+ */
+void
+pv_environ_set_env_overridable (PvEnviron *self,
+                                const char *var,
+                                const char *val)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (var != NULL);
+  /* val may be NULL, to lock it to be unset */
+
+  g_hash_table_replace (self->values, g_strdup (var), g_strdup (val));
+  g_hash_table_remove (self->locked, var);
+}
+
+static int
+generic_strcmp (gconstpointer a,
+                gconstpointer b)
+{
+  return strcmp (a, b);
+}
+
+/*
+ * Returns: (transfer container): The variables that are set or forced
+ *  to be unset, but not the variables that are locked to be inherited
+ */
+GList *
+pv_environ_get_vars (PvEnviron *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+
+  return g_list_sort (g_hash_table_get_keys (self->values), generic_strcmp);
+}
+
+/*
+ * Returns: (transfer container): The variables that are locked in some way
+ */
+GList *
+pv_environ_get_locked (PvEnviron *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+
+  return g_list_sort (g_hash_table_get_keys (self->locked), generic_strcmp);
+}
+
+/*
+ * Returns: %TRUE if @var is locked
+ */
+gboolean
+pv_environ_is_locked (PvEnviron *self,
+                      const char *var)
+{
+  g_return_val_if_fail (self != NULL, FALSE);
+
+  return g_hash_table_contains (self->locked, var);
+}
+
+/*
+ * Returns: (nullable): The value of @var, or %NULL if @var is either
+ *  locked-to-be-unset, locked-to-be-inherited or unspecified
+ */
+const char *
+pv_environ_getenv (PvEnviron *self,
+                   const char *var)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+
+  return g_hash_table_lookup (self->values, var);
+}
diff --git a/pressure-vessel/environ.h b/pressure-vessel/environ.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f3faa9c7dc343b63c5418228ae9ff90d426915f
--- /dev/null
+++ b/pressure-vessel/environ.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2014-2018 Red Hat, Inc
+ * Copyright © 2020 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "libglnx/libglnx.h"
+
+#include "steam-runtime-tools/glib-backports-internal.h"
+
+typedef struct _PvEnviron PvEnviron;
+
+PvEnviron *pv_environ_new (void);
+void pv_environ_free (PvEnviron *self);
+
+void pv_environ_lock_env (PvEnviron *self,
+                          const char *var,
+                          const char *val);
+void pv_environ_lock_inherit_env (PvEnviron *self,
+                                  const char *var);
+void pv_environ_set_env_overridable (PvEnviron *self,
+                                     const char *var,
+                                     const char *val);
+
+GList *pv_environ_get_vars (PvEnviron *self);
+GList *pv_environ_get_locked (PvEnviron *self);
+const char *pv_environ_getenv (PvEnviron *self,
+                               const char *var);
+gboolean pv_environ_is_locked (PvEnviron *self,
+                               const char *var);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvEnviron, pv_environ_free)
diff --git a/pressure-vessel/flatpak-run-private.h b/pressure-vessel/flatpak-run-private.h
index b9a7ac2ec2d9539d8e7c8ce9a5df4668e404ca2f..a5931a61e1f4435b82e126e15eee92670929d409 100644
--- a/pressure-vessel/flatpak-run-private.h
+++ b/pressure-vessel/flatpak-run-private.h
@@ -39,11 +39,9 @@
 #include "steam-runtime-tools/glib-backports-internal.h"
 
 void flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
-                               GHashTable   *extra_locked_vars_to_unset,
                                gboolean      allowed);
 gboolean flatpak_run_add_wayland_args (FlatpakBwrap *bwrap);
-void flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap,
-                                      GHashTable   *extra_locked_vars_to_unset);
+void flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap);
 gboolean flatpak_run_add_system_dbus_args (FlatpakBwrap *app_bwrap);
 gboolean flatpak_run_add_session_dbus_args (FlatpakBwrap *app_bwrap);
 void     flatpak_run_apply_env_appid (FlatpakBwrap *bwrap,
diff --git a/pressure-vessel/flatpak-run.c b/pressure-vessel/flatpak-run.c
index 5b244f72528ad0bfa9367ab28109d84c02616387..9714e56b08463a20bbefd571f798168bb1d716ea 100644
--- a/pressure-vessel/flatpak-run.c
+++ b/pressure-vessel/flatpak-run.c
@@ -160,7 +160,6 @@ write_xauth (char *number, FILE *output)
 
 void
 flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
-                          GHashTable   *extra_locked_vars_to_unset,
                           gboolean      allowed)
 {
   g_autofree char *x11_socket = NULL;
@@ -176,7 +175,7 @@ flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
 
   if (!allowed)
     {
-      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("DISPLAY"));
+      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
       return;
     }
 
@@ -230,7 +229,7 @@ flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
     }
   else
     {
-      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("DISPLAY"));
+      flatpak_bwrap_unset_env (bwrap, "DISPLAY");
     }
 }
 
@@ -534,8 +533,7 @@ flatpak_run_parse_pulse_server (const char *value)
 }
 
 void
-flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap,
-                                 GHashTable   *extra_locked_vars_to_unset)
+flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap)
 {
   g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
   g_autofree char *pulseaudio_socket = NULL;
@@ -547,7 +545,7 @@ flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap,
   if (!pulseaudio_socket)
     pulseaudio_socket = g_build_filename (user_runtime_dir, "pulse/native", NULL);
 
-  g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("PULSE_SERVER"));
+  flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");
 
   /* SteamOS system-wide PulseAudio instance */
   if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
diff --git a/pressure-vessel/meson.build b/pressure-vessel/meson.build
index ed93e41b7baf58f9634cdab8e8c348020ab666e2..2534a40c948ec2c322bb163a92ecc7f48a4a53ef 100644
--- a/pressure-vessel/meson.build
+++ b/pressure-vessel/meson.build
@@ -191,6 +191,8 @@ executable(
   sources : [
     'bwrap.c',
     'bwrap.h',
+    'environ.c',
+    'environ.h',
     'exports.c',
     'exports.h',
     'flatpak-common-types-private.h',
diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c
index 64be4c2ba97c03e368d027c25fe2508ea87bdc2a..57248775f0ed1d2178ecf84937ca87a403d0936e 100644
--- a/pressure-vessel/runtime.c
+++ b/pressure-vessel/runtime.c
@@ -350,11 +350,10 @@ G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (RuntimeArchitecture,
 
 static gboolean pv_runtime_use_provider_graphics_stack (PvRuntime *self,
                                                         FlatpakBwrap *bwrap,
-                                                        GHashTable *extra_locked_vars_to_unset,
-                                                        GHashTable *extra_locked_vars_to_inherit,
+                                                        PvEnviron *container_env,
                                                         GError **error);
 static void pv_runtime_set_search_paths (PvRuntime *self,
-                                         FlatpakBwrap *bwrap);
+                                         PvEnviron *container_env);
 
 static void
 pv_runtime_init (PvRuntime *self)
@@ -1626,8 +1625,7 @@ static gboolean
 bind_runtime (PvRuntime *self,
               FlatpakExports *exports,
               FlatpakBwrap *bwrap,
-              GHashTable *extra_locked_vars_to_unset,
-              GHashTable *extra_locked_vars_to_inherit,
+              PvEnviron *container_env,
               GError **error)
 {
   static const char * const bind_mutable[] =
@@ -1693,7 +1691,7 @@ bind_runtime (PvRuntime *self,
                           "--symlink", "../run", "/var/run",
                           NULL);
 
-  flatpak_bwrap_set_env (bwrap, "XDG_RUNTIME_DIR", xrd, TRUE);
+  pv_environ_lock_env (container_env, "XDG_RUNTIME_DIR", xrd);
 
   if (g_strcmp0 (self->provider_in_host_namespace, "/") != 0
       || g_strcmp0 (self->provider_in_container_namespace, "/run/host") != 0)
@@ -1811,8 +1809,8 @@ bind_runtime (PvRuntime *self,
 
   if (self->flags & PV_RUNTIME_FLAGS_PROVIDER_GRAPHICS_STACK)
     {
-      if (!pv_runtime_use_provider_graphics_stack (self, bwrap, extra_locked_vars_to_unset,
-                                                   extra_locked_vars_to_inherit, error))
+      if (!pv_runtime_use_provider_graphics_stack (self, bwrap, container_env,
+                                                   error))
         return FALSE;
     }
 
@@ -3169,8 +3167,7 @@ pv_runtime_finish_libc_family (PvRuntime *self,
 static gboolean
 pv_runtime_use_provider_graphics_stack (PvRuntime *self,
                                         FlatpakBwrap *bwrap,
-                                        GHashTable *extra_locked_vars_to_unset,
-                                        GHashTable *extra_locked_vars_to_inherit,
+                                        PvEnviron *container_env,
                                         GError **error)
 {
   gsize i, j;
@@ -3211,6 +3208,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
 
   g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
   g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
+  g_return_val_if_fail (container_env != NULL, FALSE);
   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
   if (!pv_runtime_provide_container_access (self, error))
@@ -3654,21 +3652,23 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
     }
 
   if (dri_path->len != 0)
-    flatpak_bwrap_set_env (bwrap, "LIBGL_DRIVERS_PATH", dri_path->str, TRUE);
+    pv_environ_lock_env (container_env, "LIBGL_DRIVERS_PATH", dri_path->str);
   else
-    g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("LIBGL_DRIVERS_PATH"));
+    pv_environ_lock_env (container_env, "LIBGL_DRIVERS_PATH", NULL);
 
   if (egl_path->len != 0)
-    flatpak_bwrap_set_env (bwrap, "__EGL_VENDOR_LIBRARY_FILENAMES", egl_path->str, TRUE);
+    pv_environ_lock_env (container_env, "__EGL_VENDOR_LIBRARY_FILENAMES",
+                         egl_path->str);
   else
-    g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("__EGL_VENDOR_LIBRARY_FILENAMES"));
+    pv_environ_lock_env (container_env, "__EGL_VENDOR_LIBRARY_FILENAMES",
+                         NULL);
 
-  g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("__EGL_VENDOR_LIBRARY_DIRS"));
+  pv_environ_lock_env (container_env, "__EGL_VENDOR_LIBRARY_DIRS", NULL);
 
   if (vulkan_path->len != 0)
-    flatpak_bwrap_set_env (bwrap, "VK_ICD_FILENAMES", vulkan_path->str, TRUE);
+    pv_environ_lock_env (container_env, "VK_ICD_FILENAMES", vulkan_path->str);
   else
-    g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("VK_ICD_FILENAMES"));
+    pv_environ_lock_env (container_env, "VK_ICD_FILENAMES", NULL);
 
   if (self->flags & PV_RUNTIME_FLAGS_IMPORT_VULKAN_LAYERS)
     {
@@ -3690,15 +3690,17 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
           else
             prepended_data_dirs = g_steal_pointer (&override_share);
 
-          flatpak_bwrap_set_env (bwrap, "XDG_DATA_DIRS", prepended_data_dirs, TRUE);
+          pv_environ_lock_env (container_env, "XDG_DATA_DIRS",
+                               prepended_data_dirs);
         }
-      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("VK_LAYER_PATH"));
+      pv_environ_lock_env (container_env, "VK_LAYER_PATH", NULL);
     }
 
   if (va_api_path->len != 0)
-    flatpak_bwrap_set_env (bwrap, "LIBVA_DRIVERS_PATH", va_api_path->str, TRUE);
+    pv_environ_lock_env (container_env, "LIBVA_DRIVERS_PATH",
+                         va_api_path->str);
   else
-    g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("LIBVA_DRIVERS_PATH"));
+    pv_environ_lock_env (container_env, "LIBVA_DRIVERS_PATH", NULL);
 
   /* We binded the VDPAU drivers in "%{libdir}/vdpau".
    * Unfortunately VDPAU_DRIVER_PATH can hold just a single path, so we can't
@@ -3709,8 +3711,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
   g_autofree gchar *vdpau_val = g_strdup_printf ("%s/lib/platform-${PLATFORM}/vdpau",
                                                  self->overrides_in_container);
 
-  flatpak_bwrap_set_env (bwrap, "VDPAU_DRIVER_PATH", vdpau_val, TRUE);
-
+  pv_environ_lock_env (container_env, "VDPAU_DRIVER_PATH", vdpau_val);
   return TRUE;
 }
 
@@ -3718,8 +3719,7 @@ gboolean
 pv_runtime_bind (PvRuntime *self,
                  FlatpakExports *exports,
                  FlatpakBwrap *bwrap,
-                 GHashTable *extra_locked_vars_to_unset,
-                 GHashTable *extra_locked_vars_to_inherit,
+                 PvEnviron *container_env,
                  GError **error)
 {
   g_autofree gchar *pressure_vessel_prefix = NULL;
@@ -3727,15 +3727,13 @@ pv_runtime_bind (PvRuntime *self,
   g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
   g_return_val_if_fail (exports != NULL, FALSE);
   g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
-  g_return_val_if_fail (extra_locked_vars_to_unset != NULL, FALSE);
-  g_return_val_if_fail (extra_locked_vars_to_inherit != NULL, FALSE);
+  g_return_val_if_fail (container_env != NULL, FALSE);
   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
   if (!bind_runtime (self,
                      exports,
                      bwrap,
-                     extra_locked_vars_to_unset,
-                     extra_locked_vars_to_inherit,
+                     container_env,
                      error))
     return FALSE;
 
@@ -3792,16 +3790,16 @@ pv_runtime_bind (PvRuntime *self,
    * backwards compatibility is not a concern for game developers who
    * have specifically opted-in to using the newer runtime. */
   if (self->is_scout)
-    flatpak_bwrap_set_env (bwrap, "STEAM_RUNTIME", "/", TRUE);
+    pv_environ_lock_env (container_env, "STEAM_RUNTIME", "/");
 
-  pv_runtime_set_search_paths (self, bwrap);
+  pv_runtime_set_search_paths (self, container_env);
 
   return TRUE;
 }
 
 void
 pv_runtime_set_search_paths (PvRuntime *self,
-                             FlatpakBwrap *bwrap)
+                             PvEnviron *container_env)
 {
   g_autoptr(GString) ld_library_path = g_string_new ("");
   gsize i;
@@ -3824,8 +3822,8 @@ pv_runtime_set_search_paths (PvRuntime *self,
 
   /* The PATH from outside the container doesn't really make sense inside the
    * container: in principle the layout could be totally different. */
-  flatpak_bwrap_set_env (bwrap, "PATH", "/usr/bin:/bin", TRUE);
-  flatpak_bwrap_set_env (bwrap, "LD_LIBRARY_PATH", ld_library_path->str, TRUE);
+  pv_environ_lock_env (container_env, "PATH", "/usr/bin:/bin");
+  pv_environ_lock_env (container_env, "LD_LIBRARY_PATH", ld_library_path->str);
 }
 
 static void
diff --git a/pressure-vessel/runtime.h b/pressure-vessel/runtime.h
index 1d4d4ea33e0de221ef3f56619735b0580eccfde7..4163d6c97354cd5925c85d09dc8417e5c7a293eb 100644
--- a/pressure-vessel/runtime.h
+++ b/pressure-vessel/runtime.h
@@ -26,6 +26,7 @@
 #include "steam-runtime-tools/glib-backports-internal.h"
 #include "libglnx/libglnx.h"
 
+#include "environ.h"
 #include "flatpak-bwrap-private.h"
 #include "flatpak-exports-private.h"
 #include "flatpak-utils-base-private.h"
@@ -85,8 +86,7 @@ gboolean pv_runtime_get_adverb (PvRuntime *self,
 gboolean pv_runtime_bind (PvRuntime *self,
                           FlatpakExports *exports,
                           FlatpakBwrap *bwrap,
-                          GHashTable *extra_locked_vars_to_unset,
-                          GHashTable *extra_locked_vars_to_inherit,
+                          PvEnviron *container_env,
                           GError **error);
 void pv_runtime_cleanup (PvRuntime *self);
 
diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c
index 24da9c776363354b37407e95c64f5436e45e9731..46d2c7162acfc9fe0b992ced2f5874f0f0389eaf 100644
--- a/pressure-vessel/wrap.c
+++ b/pressure-vessel/wrap.c
@@ -36,6 +36,7 @@
 
 #include "bwrap.h"
 #include "bwrap-lock.h"
+#include "environ.h"
 #include "flatpak-bwrap-private.h"
 #include "flatpak-run-private.h"
 #include "flatpak-utils-base-private.h"
@@ -385,7 +386,7 @@ static const EnvMount known_required_env[] =
 
 static void
 bind_and_propagate_from_environ (FlatpakExports *exports,
-                                 FlatpakBwrap *bwrap,
+                                 PvEnviron *container_env,
                                  const char *variable,
                                  EnvMountFlags flags)
 {
@@ -461,7 +462,7 @@ bind_and_propagate_from_environ (FlatpakExports *exports,
     {
       g_autofree gchar *joined = g_strjoinv (":", values);
 
-      flatpak_bwrap_set_env (bwrap, variable, joined, TRUE);
+      pv_environ_lock_env (container_env, variable, joined);
     }
 }
 
@@ -482,6 +483,7 @@ static gboolean expose_steam (FlatpakExports *exports,
 static gboolean
 use_fake_home (FlatpakExports *exports,
                FlatpakBwrap *bwrap,
+               PvEnviron *container_env,
                const gchar *fake_home,
                GError **error)
 {
@@ -542,9 +544,9 @@ use_fake_home (FlatpakExports *exports,
                           "--bind", tmp, "/var/tmp",
                           NULL);
 
-  flatpak_bwrap_set_env (bwrap, "XDG_CACHE_HOME", cache, TRUE);
-  flatpak_bwrap_set_env (bwrap, "XDG_CONFIG_HOME", config, TRUE);
-  flatpak_bwrap_set_env (bwrap, "XDG_DATA_HOME", data, TRUE);
+  pv_environ_lock_env (container_env, "XDG_CACHE_HOME", cache);
+  pv_environ_lock_env (container_env, "XDG_CONFIG_HOME", config);
+  pv_environ_lock_env (container_env, "XDG_DATA_HOME", data);
 
   flatpak_exports_add_path_expose (exports,
                                    FLATPAK_FILESYSTEM_MODE_READ_WRITE,
@@ -1211,6 +1213,7 @@ main (int argc,
   g_auto(GStrv) original_environ = NULL;
   int original_argc = argc;
   gboolean is_flatpak_env = g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR);
+  g_autoptr(PvEnviron) container_env = NULL;
   g_autoptr(FlatpakBwrap) bwrap = NULL;
   g_autoptr(FlatpakBwrap) argv_in_container = NULL;
   g_autoptr(FlatpakBwrap) final_argv = NULL;
@@ -1225,17 +1228,8 @@ main (int argc,
   g_autofree gchar *tools_dir = NULL;
   g_autoptr(PvRuntime) runtime = NULL;
   g_autoptr(FILE) original_stdout = NULL;
-  GHashTableIter iter;
-  const gchar *env_var;
   g_autoptr(GString) lock_env = g_string_new ("");
   g_auto(GLnxTmpfile) lock_env_tmpf  = { 0, };
-  gpointer k;
-  g_autoptr(GHashTable) extra_locked_vars_to_unset = g_hash_table_new_full (g_str_hash,
-                                                                            g_str_equal,
-                                                                            g_free, NULL);
-  g_autoptr(GHashTable) extra_locked_vars_to_inherit = g_hash_table_new_full (g_str_hash,
-                                                                            g_str_equal,
-                                                                            g_free, NULL);
   g_autofree char *lock_env_fd = NULL;
   g_autoptr(GArray) pass_fds_through_adverb = g_array_new (FALSE, FALSE, sizeof (int));
 
@@ -1626,6 +1620,7 @@ main (int argc,
   bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
   flatpak_bwrap_add_arg (bwrap, bwrap_executable);
   exports = flatpak_exports_new ();
+  container_env = pv_environ_new ();
 
   if (is_flatpak_env)
     {
@@ -1699,8 +1694,7 @@ main (int argc,
       if (!pv_runtime_bind (runtime,
                             exports,
                             bwrap,
-                            extra_locked_vars_to_unset,
-                            extra_locked_vars_to_inherit,
+                            container_env,
                             error))
         goto out;
     }
@@ -1779,7 +1773,8 @@ main (int argc,
     }
   else
     {
-      if (!use_fake_home (exports, bwrap, opt_fake_home, error))
+      if (!use_fake_home (exports, bwrap, container_env, opt_fake_home,
+                          error))
         goto out;
     }
 
@@ -1862,11 +1857,12 @@ main (int argc,
     }
 
   if (adjusted_ld_preload->len != 0)
-    flatpak_bwrap_set_env (bwrap, "LD_PRELOAD", adjusted_ld_preload->str, TRUE);
+    pv_environ_set_env_overridable (container_env, "LD_PRELOAD",
+                                    adjusted_ld_preload->str);
 
   g_debug ("Making Steam environment variables available if required...");
   for (i = 0; i < G_N_ELEMENTS (known_required_env); i++)
-    bind_and_propagate_from_environ (exports, bwrap,
+    bind_and_propagate_from_environ (exports, container_env,
                                      known_required_env[i].name,
                                      known_required_env[i].flags);
 
@@ -1920,7 +1916,7 @@ main (int argc,
                           "--chdir", cwd_p_host,
                           NULL);
 
-  g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("PWD"));
+  pv_environ_lock_env (container_env, "PWD", NULL);
 
   /* Put Steam Runtime environment variables back, if /usr is mounted
    * from the host. */
@@ -1945,7 +1941,8 @@ main (int argc,
 
               *equals = '\0';
 
-              flatpak_bwrap_set_env (bwrap, opt_env_if_host[i], equals + 1, TRUE);
+              pv_environ_lock_env (container_env, opt_env_if_host[i],
+                                   equals + 1);
 
               *equals = '=';
             }
@@ -1968,6 +1965,16 @@ main (int argc,
     {
       g_autoptr(FlatpakBwrap) sharing_bwrap =
         flatpak_bwrap_new (flatpak_bwrap_empty_env);
+      g_auto(GStrv) envp = NULL;
+
+      /* If these are set by flatpak_run_add_x11_args(), etc., we'll
+       * change them from locked-and-unset to locked-and-set later.
+       * Every variable that is unset with flatpak_bwrap_unset_env() in
+       * the functions we borrow from Flatpak (below) should be listed
+       * here. */
+      pv_environ_lock_env (container_env, "DISPLAY", NULL);
+      pv_environ_lock_env (container_env, "PULSE_SERVER", NULL);
+      pv_environ_lock_env (container_env, "XAUTHORITY", NULL);
 
       flatpak_run_add_font_path_args (sharing_bwrap);
 
@@ -1991,132 +1998,159 @@ main (int argc,
             }
           else
             {
-              flatpak_run_add_x11_args (sharing_bwrap, extra_locked_vars_to_unset, TRUE);
+              flatpak_run_add_x11_args (sharing_bwrap, TRUE);
             }
 
-          flatpak_run_add_pulseaudio_args (sharing_bwrap, extra_locked_vars_to_unset);
+          flatpak_run_add_pulseaudio_args (sharing_bwrap);
           flatpak_run_add_session_dbus_args (sharing_bwrap);
           flatpak_run_add_system_dbus_args (sharing_bwrap);
         }
 
+      envp = pv_bwrap_steal_envp (sharing_bwrap);
+
+      for (i = 0; envp[i] != NULL; i++)
+        {
+          static const char * const known_vars[] =
+          {
+            "DBUS_SESSION_BUS_ADDRESS",
+            "DBUS_SYSTEM_BUS_ADDRESS",
+            "DISPLAY",
+            "PULSE_CLIENTCONFIG",
+            "PULSE_SERVER",
+            "XAUTHORITY",
+          };
+          char *equals = strchr (envp[i], '=');
+          const char *var = envp[i];
+          const char *val = NULL;
+          gsize j;
+
+          if (equals != NULL)
+            {
+              *equals = '\0';
+              val = equals + 1;
+            }
+
+          for (j = 0; j < G_N_ELEMENTS (known_vars); j++)
+            {
+              if (strcmp (var, known_vars[j]) == 0)
+                break;
+            }
+
+          /* If this warning is reached, we might need to add this
+           * variable to the block of
+           * pv_environ_lock_env (container_env, ., NULL) calls above */
+          if (j >= G_N_ELEMENTS (known_vars))
+            g_warning ("Extra environment variable %s set during container "
+                       "setup but not in known_vars; check logic",
+                       var);
+
+          pv_environ_lock_env (container_env, var, val);
+        }
+
+      g_warn_if_fail (g_strv_length (sharing_bwrap->envp) == 0);
       flatpak_bwrap_append_bwrap (bwrap, sharing_bwrap);
     }
 
   if (is_flatpak_env)
     {
+      g_autoptr(GList) vars = NULL;
+      const GList *iter;
+
       /* These are the environment variables that will be wrong, or useless,
-       * in the new container that will be created. */
-      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("FLATPAK_ID"));
-      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("FLATPAK_SANDBOX_DIR"));
-      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("LD_AUDIT"));
+       * in the new container that will be created by escaping from the
+       * sandbox. Force them to be unset. */
+      pv_environ_lock_env (container_env, "FLATPAK_ID", NULL);
+      pv_environ_lock_env (container_env, "FLATPAK_SANDBOX_DIR", NULL);
+      pv_environ_lock_env (container_env, "LD_AUDIT", NULL);
 
       /* These are the environment variables that might differ in the host
        * system. However from inside a container we are not able to know the
        * host's value. So we allow them to inherit the value from the host. */
-      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("DBUS_SESSION_BUS_ADDRESS"));
-      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("DBUS_SYSTEM_BUS_ADDRESS"));
-      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("DISPLAY"));
-      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("XDG_RUNTIME_DIR"));
+      pv_environ_lock_inherit_env (container_env, "DBUS_SESSION_BUS_ADDRESS");
+      pv_environ_lock_inherit_env (container_env, "DBUS_SYSTEM_BUS_ADDRESS");
+      pv_environ_lock_inherit_env (container_env, "DISPLAY");
+      pv_environ_lock_inherit_env (container_env, "XDG_RUNTIME_DIR");
 
       /* The bwrap envp will be completely ignored when calling
        * pv-launch, and in fact putting them in its environment
        * variables would be wrong, because pv-launch needs to see the
        * current execution environment's DBUS_SESSION_BUS_ADDRESS
        * (if different). For this reason we convert them to `--setenv`. */
-      for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++)
-        {
-          g_auto(GStrv) split = g_strsplit (bwrap->envp[i], "=", 2);
+      vars = pv_environ_get_vars (container_env);
 
-          g_assert (split != NULL);
-          g_assert (split[0] != NULL);
-          g_assert (split[1] != NULL);
+      for (iter = vars; iter != NULL; iter = iter->next)
+        {
+          const char *var = iter->data;
+          const char *val = pv_environ_getenv (container_env, var);
 
-          flatpak_bwrap_add_args (bwrap,
-                                  "--setenv", split[0], split[1],
-                                  NULL);
+          if (val != NULL)
+            flatpak_bwrap_add_args (bwrap,
+                                    "--setenv", var, val,
+                                    NULL);
+          else
+            flatpak_bwrap_add_args (bwrap,
+                                    "--unsetenv", var,
+                                    NULL);
         }
     }
 
-  for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++)
-    {
-      /* List only the variables names and not their values */
-      char *equals = strchr (bwrap->envp[i], '=');
-
-      g_assert (equals != NULL);
-
-      /* Never lock LD_PRELOAD, otherwise, for example, we might miss the
-       * overlay renderer library that the Steam client adds to LD_PRELOAD */
-      if (g_str_has_prefix (bwrap->envp[i], "LD_PRELOAD="))
-        continue;
-
-      *equals = '\0';
+  final_argv = flatpak_bwrap_new (original_environ);
 
-      g_string_append (lock_env, bwrap->envp[i]);
-
-      *equals = '=';
-
-      g_string_append_c (lock_env, '\0');
-    }
-
-  g_hash_table_iter_init (&iter, extra_locked_vars_to_unset);
-  while (g_hash_table_iter_next (&iter, &k, NULL))
+  /* Lock variables where appropriate */
     {
-      env_var = k;
-      g_string_append (lock_env, env_var);
-      g_string_append_c (lock_env, '\0');
+      g_autoptr(GList) vars = pv_environ_get_locked (container_env);
+      const GList *iter;
 
-      flatpak_bwrap_add_args (bwrap,
-                              "--unsetenv", env_var,
-                              NULL);
-    }
+      for (iter = vars; iter != NULL; iter = iter->next)
+        {
+          const char *var = iter->data;
 
-  g_hash_table_iter_init (&iter, extra_locked_vars_to_inherit);
-  while (g_hash_table_iter_next (&iter, &k, NULL))
-    {
-      /* To inherit the variable value, we just use the lock
-       * mechanism without `--setenv` or '--unsetenv'. In this way, when we
-       * execute `launcher` with `--host`, it will inherit the value from the
-       * host system. */
-      env_var = k;
-      g_string_append (lock_env, env_var);
-      g_string_append_c (lock_env, '\0');
+          g_debug ("Locking environment variable: %s", var);
+          g_string_append (lock_env, var);
+          g_string_append_c (lock_env, '\0');
+        }
     }
 
-  /* Populate the bwrap environ with the variables that are not in the
-   * `extra_locked_vars_to_unset` list or `extra_locked_vars_to_inherit` list,
-   * and without overriding the values that are already present in
-   * bwrap->envp.
+  /* Populate final_argv->envp, overwriting its copy of original_environ.
    * We skip this if we are in a Flatpak environment, because in that case
    * we already used `--setenv` for all the variables that we care about and
-   * the bwrap envp will be ignored anyway. */
+   * the final_argv->envp will be ignored anyway, other than as a way to
+   * invoke pv-launch (for which original_environ is appropriate). */
   if (!is_flatpak_env)
     {
-      for (i = 0; original_environ[i] != NULL; i++)
+      g_autoptr(GList) vars = NULL;
+      const GList *iter;
+
+      vars = pv_environ_get_vars (container_env);
+
+      for (iter = vars; iter != NULL; iter = iter->next)
         {
-          char *eq = strchr (original_environ[i], '=');
-          if (eq)
-            {
-              g_autofree gchar *key = g_strndup (original_environ[i],
-                                                 eq - original_environ[i]);
-              if (!g_hash_table_contains (extra_locked_vars_to_unset, key)
-                  && !g_hash_table_contains (extra_locked_vars_to_inherit, key))
-                flatpak_bwrap_set_env (bwrap, key, eq + 1, FALSE);
-            }
+          const char *var = iter->data;
+          const char *val = pv_environ_getenv (container_env, var);
+
+          if (val != NULL)
+            flatpak_bwrap_set_env (final_argv, var, val, TRUE);
+          else
+            flatpak_bwrap_unset_env (final_argv, var);
         }
 
       /* The setuid bwrap will filter out some of the environment variables,
-       * so we have to go via --setenv */
+       * so we still have to go via --setenv for these. */
       for (i = 0; unsecure_environment_variables[i] != NULL; i++)
         {
-          const gchar *val = g_environ_getenv (bwrap->envp, unsecure_environment_variables[i]);
+          const char *var = unsecure_environment_variables[i];
+          const gchar *val = pv_environ_getenv (container_env, var);
+
           if (val != NULL)
-            {
-              flatpak_bwrap_add_args (bwrap, "--setenv",
-                                      unsecure_environment_variables[i], val, NULL);
-            }
+            flatpak_bwrap_add_args (bwrap, "--setenv", var, val, NULL);
         }
     }
 
+  /* Now that we've populated final_argv->envp, it's too late to change
+   * any environment variables. Make sure we get an assertion failure
+   * if we try. */
+  g_clear_pointer (&container_env, pv_environ_free);
+
   if (!flatpak_buffer_to_sealed_memfd_or_tmpfile (&lock_env_tmpf, "lock-env",
                                                   lock_env->str, lock_env->len,
                                                   error))
@@ -2297,6 +2331,7 @@ main (int argc,
 
       flatpak_bwrap_add_arg (adverb_argv, "--");
 
+      g_warn_if_fail (g_strv_length (adverb_argv->envp) == 0);
       flatpak_bwrap_append_bwrap (argv_in_container, adverb_argv);
     }
 
@@ -2321,6 +2356,7 @@ main (int argc,
        * passed to the launcher */
       flatpak_bwrap_append_argsv (launcher_argv, &argv[1], argc - 1);
 
+      g_warn_if_fail (g_strv_length (launcher_argv->envp) == 0);
       flatpak_bwrap_append_bwrap (argv_in_container, launcher_argv);
     }
   else
@@ -2333,18 +2369,16 @@ main (int argc,
       flatpak_bwrap_append_argsv (argv_in_container, &argv[1], argc - 1);
     }
 
+  g_warn_if_fail (g_strv_length (argv_in_container->envp) == 0);
   flatpak_bwrap_append_bwrap (bwrap, argv_in_container);
 
-  final_argv = flatpak_bwrap_new (flatpak_bwrap_empty_env);
+  g_warn_if_fail (g_strv_length (bwrap->envp) == 0);
 
   if (is_flatpak_env)
     {
-      /* Use pv-launch to launch bwrap on the host. This needs to run
-       * with the environment variables from the current execution
-       * environment (not the container that we are setting up!)
-       * so that it gets the right DBUS_SESSION_BUS_ADDRESS, if
-       * different. */
-      g_autoptr(FlatpakBwrap) launch_on_host = flatpak_bwrap_new (original_environ);
+      /* Use pv-launch to launch bwrap on the host. */
+      g_autoptr(FlatpakBwrap) launch_on_host =
+        flatpak_bwrap_new (flatpak_bwrap_empty_env);
       flatpak_bwrap_add_arg (launch_on_host, launch_executable);
       flatpak_bwrap_add_arg (launch_on_host, "--bus-name=org.freedesktop.Flatpak");
 
@@ -2361,15 +2395,18 @@ main (int argc,
 
       flatpak_bwrap_add_arg (launch_on_host, "--");
 
-      /* Don't use the environment variables from @bwrap any more.
-       * We already converted them to --setenv and locked them. */
-      g_strfreev (pv_bwrap_steal_envp (bwrap));
-
+      g_warn_if_fail (g_strv_length (launch_on_host->envp) == 0);
       flatpak_bwrap_append_bwrap (final_argv, launch_on_host);
     }
 
   flatpak_bwrap_append_bwrap (final_argv, bwrap);
 
+  /* We'll have permuted the order anyway, so we might as well sort it,
+   * to make debugging a bit easier. */
+  if (final_argv->envp != NULL)
+    qsort (final_argv->envp, g_strv_length (final_argv->envp),
+           sizeof (char *), pv_envp_cmp);
+
   if (opt_verbose)
     {
       g_message ("Final command to execute:");