You could try building a custom image where everything is owned by root apart from wherever the user’s volume is mounted, so it’s impossible for anything to be changed outside that directory. You may also need some Conda or Python configuration to force them to use that directory, but you can try without first.
DockerSpawner uses the standard Docker API, same as the Docker command-line client, so you can try out different volume configurations locally without JupyterHub using just docker run
, then work out what the equivalent DockerSpawner config is.