We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unable to disable user config with Jupyter Server
The hub administrator is supposed to be able to prevent per-user notebook configuration scripts from running by setting

```
c.Spawner.disable_user_config = True
```

In the `jupyterhub_config.py` config.  This sets the environment variable `JUPYTERHUB_DISABLE_USER_CONFIG=1` for the spawned notebook server.  However this seems to be being ignored?

<details>
<summary>Using this Dockerfile</summary>

```
FROM jupyterhub/jupyterhub:2

RUN python3 -m pip install --no-cache jupyterlab

RUN \
    adduser -q --gecos "" --disabled-password user1     && \
    echo user1:user1 | chpasswd

ADD jupyterhub_config.py .

RUN mkdir -p /home/user1/.jupyter
ADD jupyter_notebook_config.py /home/user1/.jupyter/.
RUN chown -R user1:user1 /home/user1/.jupyter

CMD ["jupyterhub"]
```

</details>

<details><summary>
with this `jupyterhub_config.py` and example notebook config for `user1`:
</summary>

```
c.Spawner.disable_user_config = True
```

```
import os

print("HELLO FROM THE NOTEBOOK CONFIG")
print(os.getenv("JUPYTERHUB_DISABLE_USER_CONFIG"))

c.ServerApp.shutdown_no_activity_timeout = 600
c.MappingKernelManager.cull_idle_timeout = 600
c.TerminalManager.cull_inactive_timeout = 600
```

</details>

I see the "HELLO" message and the value 1 printed when the notebook starts up, and the timeout message indicating that my config setting is in effect:

```
[I 2022-02-22 22:35:23.167 SingleUserLabApp serverapp:2161] Will shut down after 600 seconds with no kernels or terminals.
```

Am I misunderstanding exactly what config files are excluded?  I see there's a test for this but I wonder is it actually verifying that the config is being ignored?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `jupyterhub/singleuser/_disable_user_config.py`
Content:
```
1 """
2 Disable user-controlled config for single-user servers
3 
4 Applies patches to prevent loading configuration from the user's home directory.
5 
6 Only used when launching a single-user server with disable_user_config=True.
7 
8 This is where we still have some monkeypatches,
9 because we want to prevent loading configuration from user directories,
10 and `jupyter_core` functions don't allow that.
11 
12 Due to extensions, we aren't able to apply patches in one place on the ServerApp,
13 we have to insert the patches at the lowest-level
14 on function objects themselves,
15 to ensure we modify calls to e.g. `jupyter_core.jupyter_path`
16 that may have been imported already!
17 
18 We should perhaps ask for the necessary hooks to modify this in jupyter_core,
19 rather than keeing these monkey patches around.
20 """
21 
22 import os
23 
24 from jupyter_core import paths
25 
26 
27 def _exclude_home(path_list):
28     """Filter out any entries in a path list that are in my home directory.
29 
30     Used to disable per-user configuration.
31     """
32     home = os.path.expanduser('~/')
33     for p in path_list:
34         if not p.startswith(home):
35             yield p
36 
37 
38 # record patches
39 _original_jupyter_paths = None
40 _jupyter_paths_without_home = None
41 
42 
43 def _disable_user_config(serverapp):
44     """
45     disable user-controlled sources of configuration
46     by excluding directories in their home from paths.
47 
48     This _does not_ disable frontend config,
49     such as UI settings persistence.
50 
51     1. Python config file paths
52     2. Search paths for extensions, etc.
53     3. import path
54     """
55     original_jupyter_path = paths.jupyter_path()
56     jupyter_path_without_home = list(_exclude_home(original_jupyter_path))
57 
58     # config_file_paths is a property without a setter
59     # can't override on the instance
60     default_config_file_paths = serverapp.config_file_paths
61     config_file_paths = list(_exclude_home(default_config_file_paths))
62     serverapp.__class__.config_file_paths = property(
63         lambda self: config_file_paths,
64     )
65     # verify patch applied
66     assert serverapp.config_file_paths == config_file_paths
67 
68     # patch jupyter_path to exclude $HOME
69     global _original_jupyter_paths, _jupyter_paths_without_home, _original_jupyter_config_dir
70     _original_jupyter_paths = paths.jupyter_path()
71     _jupyter_paths_without_home = list(_exclude_home(_original_jupyter_paths))
72 
73     def get_jupyter_path_without_home(*subdirs):
74         # reimport because of our `__code__` patch
75         # affects what is resolved as the parent namespace
76         from jupyterhub.singleuser._disable_user_config import (
77             _jupyter_paths_without_home,
78         )
79 
80         paths = list(_jupyter_paths_without_home)
81         if subdirs:
82             paths = [os.path.join(p, *subdirs) for p in paths]
83         return paths
84 
85     # patch `jupyter_path.__code__` to ensure all callers are patched,
86     # even if they've already imported
87     # this affects e.g. nbclassic.nbextension_paths
88     paths.jupyter_path.__code__ = get_jupyter_path_without_home.__code__
89 
90     # same thing for config_dir,
91     # which applies to some things like ExtensionApp config paths
92     # and nbclassic.static_custom_path
93 
94     # allows explicit override if $JUPYTER_CONFIG_DIR is set
95     # or config dir is otherwise not in $HOME
96 
97     if not os.getenv("JUPYTER_CONFIG_DIR") and not list(
98         _exclude_home([paths.jupyter_config_dir()])
99     ):
100         # patch specifically Application.config_dir
101         # this affects ServerApp and ExtensionApp,
102         # but does not affect JupyterLab's user-settings, etc.
103         # patching the traitlet directly affects all instances,
104         # already-created or future
105         from jupyter_core.application import JupyterApp
106 
107         def get_env_config_dir(obj, cls=None):
108             return paths.ENV_CONFIG_PATH[0]
109 
110         JupyterApp.config_dir.get = get_env_config_dir
111 
112     # record disabled state on app object
113     serverapp.disable_user_config = True
114
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/jupyterhub/singleuser/_disable_user_config.py b/jupyterhub/singleuser/_disable_user_config.py
--- a/jupyterhub/singleuser/_disable_user_config.py
+++ b/jupyterhub/singleuser/_disable_user_config.py
@@ -20,19 +20,35 @@
 """
 
 import os
+from pathlib import Path
 
 from jupyter_core import paths
 
 
+def _is_relative_to(path, prefix):
+    """
+    Backport Path.is_relative_to for Python < 3.9
+
+    added in Python 3.9
+    """
+    if hasattr(path, "is_relative_to"):
+        # Python >= 3.9
+        return path.is_relative_to(prefix)
+    else:
+        return path == prefix or prefix in path.parents
+
+
 def _exclude_home(path_list):
     """Filter out any entries in a path list that are in my home directory.
 
     Used to disable per-user configuration.
     """
-    home = os.path.expanduser('~/')
-    for p in path_list:
-        if not p.startswith(home):
-            yield p
+    # resolve paths before comparison
+    # so we do the right thing when $HOME is a symlink
+    home = Path.home().resolve()
+    for path in path_list:
+        if not _is_relative_to(Path(path).resolve(), home):
+            yield path
 
 
 # record patches

{"golden_diff": "diff --git a/jupyterhub/singleuser/_disable_user_config.py b/jupyterhub/singleuser/_disable_user_config.py\n--- a/jupyterhub/singleuser/_disable_user_config.py\n+++ b/jupyterhub/singleuser/_disable_user_config.py\n@@ -20,19 +20,35 @@\n \"\"\"\n \n import os\n+from pathlib import Path\n \n from jupyter_core import paths\n \n \n+def _is_relative_to(path, prefix):\n+    \"\"\"\n+    Backport Path.is_relative_to for Python < 3.9\n+\n+    added in Python 3.9\n+    \"\"\"\n+    if hasattr(path, \"is_relative_to\"):\n+        # Python >= 3.9\n+        return path.is_relative_to(prefix)\n+    else:\n+        return path == prefix or prefix in path.parents\n+\n+\n def _exclude_home(path_list):\n     \"\"\"Filter out any entries in a path list that are in my home directory.\n \n     Used to disable per-user configuration.\n     \"\"\"\n-    home = os.path.expanduser('~/')\n-    for p in path_list:\n-        if not p.startswith(home):\n-            yield p\n+    # resolve paths before comparison\n+    # so we do the right thing when $HOME is a symlink\n+    home = Path.home().resolve()\n+    for path in path_list:\n+        if not _is_relative_to(Path(path).resolve(), home):\n+            yield path\n \n \n # record patches\n", "issue": "Unable to disable user config with Jupyter Server\nThe hub administrator is supposed to be able to prevent per-user notebook configuration scripts from running by setting\r\n\r\n```\r\nc.Spawner.disable_user_config = True\r\n```\r\n\r\nIn the `jupyterhub_config.py` config.  This sets the environment variable `JUPYTERHUB_DISABLE_USER_CONFIG=1` for the spawned notebook server.  However this seems to be being ignored?\r\n\r\n<details>\r\n<summary>Using this Dockerfile</summary>\r\n\r\n```\r\nFROM jupyterhub/jupyterhub:2\r\n\r\nRUN python3 -m pip install --no-cache jupyterlab\r\n\r\nRUN \\\r\n    adduser -q --gecos \"\" --disabled-password user1     && \\\r\n    echo user1:user1 | chpasswd\r\n\r\nADD jupyterhub_config.py .\r\n\r\nRUN mkdir -p /home/user1/.jupyter\r\nADD jupyter_notebook_config.py /home/user1/.jupyter/.\r\nRUN chown -R user1:user1 /home/user1/.jupyter\r\n\r\nCMD [\"jupyterhub\"]\r\n```\r\n\r\n</details>\r\n\r\n<details><summary>\r\nwith this `jupyterhub_config.py` and example notebook config for `user1`:\r\n</summary>\r\n\r\n```\r\nc.Spawner.disable_user_config = True\r\n```\r\n\r\n```\r\nimport os\r\n\r\nprint(\"HELLO FROM THE NOTEBOOK CONFIG\")\r\nprint(os.getenv(\"JUPYTERHUB_DISABLE_USER_CONFIG\"))\r\n\r\nc.ServerApp.shutdown_no_activity_timeout = 600\r\nc.MappingKernelManager.cull_idle_timeout = 600\r\nc.TerminalManager.cull_inactive_timeout = 600\r\n```\r\n\r\n</details>\r\n\r\nI see the \"HELLO\" message and the value 1 printed when the notebook starts up, and the timeout message indicating that my config setting is in effect:\r\n\r\n```\r\n[I 2022-02-22 22:35:23.167 SingleUserLabApp serverapp:2161] Will shut down after 600 seconds with no kernels or terminals.\r\n```\r\n\r\nAm I misunderstanding exactly what config files are excluded?  I see there's a test for this but I wonder is it actually verifying that the config is being ignored?\n", "before_files": [{"content": "\"\"\"\nDisable user-controlled config for single-user servers\n\nApplies patches to prevent loading configuration from the user's home directory.\n\nOnly used when launching a single-user server with disable_user_config=True.\n\nThis is where we still have some monkeypatches,\nbecause we want to prevent loading configuration from user directories,\nand `jupyter_core` functions don't allow that.\n\nDue to extensions, we aren't able to apply patches in one place on the ServerApp,\nwe have to insert the patches at the lowest-level\non function objects themselves,\nto ensure we modify calls to e.g. `jupyter_core.jupyter_path`\nthat may have been imported already!\n\nWe should perhaps ask for the necessary hooks to modify this in jupyter_core,\nrather than keeing these monkey patches around.\n\"\"\"\n\nimport os\n\nfrom jupyter_core import paths\n\n\ndef _exclude_home(path_list):\n    \"\"\"Filter out any entries in a path list that are in my home directory.\n\n    Used to disable per-user configuration.\n    \"\"\"\n    home = os.path.expanduser('~/')\n    for p in path_list:\n        if not p.startswith(home):\n            yield p\n\n\n# record patches\n_original_jupyter_paths = None\n_jupyter_paths_without_home = None\n\n\ndef _disable_user_config(serverapp):\n    \"\"\"\n    disable user-controlled sources of configuration\n    by excluding directories in their home from paths.\n\n    This _does not_ disable frontend config,\n    such as UI settings persistence.\n\n    1. Python config file paths\n    2. Search paths for extensions, etc.\n    3. import path\n    \"\"\"\n    original_jupyter_path = paths.jupyter_path()\n    jupyter_path_without_home = list(_exclude_home(original_jupyter_path))\n\n    # config_file_paths is a property without a setter\n    # can't override on the instance\n    default_config_file_paths = serverapp.config_file_paths\n    config_file_paths = list(_exclude_home(default_config_file_paths))\n    serverapp.__class__.config_file_paths = property(\n        lambda self: config_file_paths,\n    )\n    # verify patch applied\n    assert serverapp.config_file_paths == config_file_paths\n\n    # patch jupyter_path to exclude $HOME\n    global _original_jupyter_paths, _jupyter_paths_without_home, _original_jupyter_config_dir\n    _original_jupyter_paths = paths.jupyter_path()\n    _jupyter_paths_without_home = list(_exclude_home(_original_jupyter_paths))\n\n    def get_jupyter_path_without_home(*subdirs):\n        # reimport because of our `__code__` patch\n        # affects what is resolved as the parent namespace\n        from jupyterhub.singleuser._disable_user_config import (\n            _jupyter_paths_without_home,\n        )\n\n        paths = list(_jupyter_paths_without_home)\n        if subdirs:\n            paths = [os.path.join(p, *subdirs) for p in paths]\n        return paths\n\n    # patch `jupyter_path.__code__` to ensure all callers are patched,\n    # even if they've already imported\n    # this affects e.g. nbclassic.nbextension_paths\n    paths.jupyter_path.__code__ = get_jupyter_path_without_home.__code__\n\n    # same thing for config_dir,\n    # which applies to some things like ExtensionApp config paths\n    # and nbclassic.static_custom_path\n\n    # allows explicit override if $JUPYTER_CONFIG_DIR is set\n    # or config dir is otherwise not in $HOME\n\n    if not os.getenv(\"JUPYTER_CONFIG_DIR\") and not list(\n        _exclude_home([paths.jupyter_config_dir()])\n    ):\n        # patch specifically Application.config_dir\n        # this affects ServerApp and ExtensionApp,\n        # but does not affect JupyterLab's user-settings, etc.\n        # patching the traitlet directly affects all instances,\n        # already-created or future\n        from jupyter_core.application import JupyterApp\n\n        def get_env_config_dir(obj, cls=None):\n            return paths.ENV_CONFIG_PATH[0]\n\n        JupyterApp.config_dir.get = get_env_config_dir\n\n    # record disabled state on app object\n    serverapp.disable_user_config = True\n", "path": "jupyterhub/singleuser/_disable_user_config.py"}], "after_files": [{"content": "\"\"\"\nDisable user-controlled config for single-user servers\n\nApplies patches to prevent loading configuration from the user's home directory.\n\nOnly used when launching a single-user server with disable_user_config=True.\n\nThis is where we still have some monkeypatches,\nbecause we want to prevent loading configuration from user directories,\nand `jupyter_core` functions don't allow that.\n\nDue to extensions, we aren't able to apply patches in one place on the ServerApp,\nwe have to insert the patches at the lowest-level\non function objects themselves,\nto ensure we modify calls to e.g. `jupyter_core.jupyter_path`\nthat may have been imported already!\n\nWe should perhaps ask for the necessary hooks to modify this in jupyter_core,\nrather than keeing these monkey patches around.\n\"\"\"\n\nimport os\nfrom pathlib import Path\n\nfrom jupyter_core import paths\n\n\ndef _is_relative_to(path, prefix):\n    \"\"\"\n    Backport Path.is_relative_to for Python < 3.9\n\n    added in Python 3.9\n    \"\"\"\n    if hasattr(path, \"is_relative_to\"):\n        # Python >= 3.9\n        return path.is_relative_to(prefix)\n    else:\n        return path == prefix or prefix in path.parents\n\n\ndef _exclude_home(path_list):\n    \"\"\"Filter out any entries in a path list that are in my home directory.\n\n    Used to disable per-user configuration.\n    \"\"\"\n    # resolve paths before comparison\n    # so we do the right thing when $HOME is a symlink\n    home = Path.home().resolve()\n    for path in path_list:\n        if not _is_relative_to(Path(path).resolve(), home):\n            yield path\n\n\n# record patches\n_original_jupyter_paths = None\n_jupyter_paths_without_home = None\n\n\ndef _disable_user_config(serverapp):\n    \"\"\"\n    disable user-controlled sources of configuration\n    by excluding directories in their home from paths.\n\n    This _does not_ disable frontend config,\n    such as UI settings persistence.\n\n    1. Python config file paths\n    2. Search paths for extensions, etc.\n    3. import path\n    \"\"\"\n    original_jupyter_path = paths.jupyter_path()\n    jupyter_path_without_home = list(_exclude_home(original_jupyter_path))\n\n    # config_file_paths is a property without a setter\n    # can't override on the instance\n    default_config_file_paths = serverapp.config_file_paths\n    config_file_paths = list(_exclude_home(default_config_file_paths))\n    serverapp.__class__.config_file_paths = property(\n        lambda self: config_file_paths,\n    )\n    # verify patch applied\n    assert serverapp.config_file_paths == config_file_paths\n\n    # patch jupyter_path to exclude $HOME\n    global _original_jupyter_paths, _jupyter_paths_without_home, _original_jupyter_config_dir\n    _original_jupyter_paths = paths.jupyter_path()\n    _jupyter_paths_without_home = list(_exclude_home(_original_jupyter_paths))\n\n    def get_jupyter_path_without_home(*subdirs):\n        # reimport because of our `__code__` patch\n        # affects what is resolved as the parent namespace\n        from jupyterhub.singleuser._disable_user_config import (\n            _jupyter_paths_without_home,\n        )\n\n        paths = list(_jupyter_paths_without_home)\n        if subdirs:\n            paths = [os.path.join(p, *subdirs) for p in paths]\n        return paths\n\n    # patch `jupyter_path.__code__` to ensure all callers are patched,\n    # even if they've already imported\n    # this affects e.g. nbclassic.nbextension_paths\n    paths.jupyter_path.__code__ = get_jupyter_path_without_home.__code__\n\n    # same thing for config_dir,\n    # which applies to some things like ExtensionApp config paths\n    # and nbclassic.static_custom_path\n\n    # allows explicit override if $JUPYTER_CONFIG_DIR is set\n    # or config dir is otherwise not in $HOME\n\n    if not os.getenv(\"JUPYTER_CONFIG_DIR\") and not list(\n        _exclude_home([paths.jupyter_config_dir()])\n    ):\n        # patch specifically Application.config_dir\n        # this affects ServerApp and ExtensionApp,\n        # but does not affect JupyterLab's user-settings, etc.\n        # patching the traitlet directly affects all instances,\n        # already-created or future\n        from jupyter_core.application import JupyterApp\n\n        def get_env_config_dir(obj, cls=None):\n            return paths.ENV_CONFIG_PATH[0]\n\n        JupyterApp.config_dir.get = get_env_config_dir\n\n    # record disabled state on app object\n    serverapp.disable_user_config = True\n", "path": "jupyterhub/singleuser/_disable_user_config.py"}]}