Let me start by saying that I think you should really consider an alternative such as using a subprocess run as a more restricted user. If your hands are tied and you're being forced to implement it this way for some reason, you might want to look into importlib hooks (after you go on the record stating it's a bad idea to solve the problem using import hooks).
From PEP 302 - New Import Hooks
There are two types of import hooks: Meta hooks and Path hooks. Meta hooks are called at the start of import processing, before any other import processing (so that meta hooks can override sys.path processing, frozen modules, or even built-in modules). To register a meta hook, simply add the finder object to sys.meta_path (the list of registered meta hooks.)
importlib.abc.MetaPathFinder
An abstract base class representing a meta path finder. For
compatibility, this is a subclass of Finder.
New in version 3.3.
find_spec(fullname, path, target=None)
An abstract method for finding a spec for the specified module. If
this is a top-level import, path will be None. Otherwise, this is a
search for a subpackage or module and path will be the value of
path from the parent package. If a spec cannot be found, None is returned. When passed in, target is a module object that the finder
may use to make a more educated guess about what spec to return.
importlib.util.spec_from_loader() may be useful for implementing
concrete MetaPathFinders.
New in version 3.4.
find_module(fullname, path)
A legacy method for finding a loader for the specified module. If this is a top-level import, path will be None. Otherwise, this is a search for a subpackage or module and path will be the value of path from the parent package. If a loader cannot be found, None is returned.
If find_spec() is defined, backwards-compatible functionality is provided.
Changed in version 3.4: Returns None when called instead of raising
NotImplementedError. Can use find_spec() to provide functionality.
Deprecated since version 3.4: Use find_spec() instead.
invalidate_caches()
An optional method which, when called, should
invalidate any internal cache used by the finder. Used by
importlib.invalidate_caches() when invalidating the caches of all
finders on sys.meta_path.
Changed in version 3.4: Returns None when called instead of
NotImplemented.
Just playing around with it a little bit, I was able to make an import hook that will check if the requested module is in a list of approved modules. If it is, it imports the module using the default importers. If not, it raises an error.
I can think of two major problems (and there are surely more) with a solution like this. First, a top-level module can import dozens of other modules. Second, if you ever need to allow a script to import sys or importlib, they could easily find a way around the import restrictions.
import sys
import importlib
class MyImporter():
original_importers = sys.meta_path
approved = ['os']
@classmethod
def find_spec(cls, fullname, path, target=None):
if fullname not in cls.approved:
raise RuntimeError('Permission for module {} not approved'.format(fullname))
for importer in cls.original_importers:
spec = importer.find_spec(fullname, path, target)
if spec is not None:
return spec
return None
if __name__ == '__main__':
importlib.abc.MetaPathFinder.register(MyImporter)
sys.meta_path = [MyImporter]
import os
import argparse # fails because argparse is not in approved modules