-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Studio: harden auto permission network gates #7180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
eed25b3
ec26768
13e9426
976b946
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,8 @@ | |
| """ | ||
|
|
||
| import builtins | ||
| import importlib | ||
| import importlib.util | ||
| import io | ||
| import json | ||
| import os | ||
|
|
@@ -46,6 +48,140 @@ | |
| # on-disk sidecar carries the map across runs. It records only sources the | ||
| # fallback healed, so an unrelated same-basename file is never adopted. | ||
| _REMAP_SIDECAR = ".unsloth_sandbox_remap.json" | ||
| _BLOCKED_NETWORK_MODULES = frozenset({"boto3", "botocore"}) | ||
| # httpx imports httpcore internally, so block only sandbox-user requests. | ||
| _DIRECT_BLOCKED_NETWORK_MODULES = frozenset({"httpcore"}) | ||
| _import_guard_installed = False | ||
| _original_import = builtins.__import__ | ||
| _original_import_module = importlib.import_module | ||
|
|
||
|
|
||
| def _path_is_in_sandbox(filename): | ||
| if not isinstance(filename, str) or filename.startswith("<"): | ||
| return False | ||
| try: | ||
| cwd = os.path.realpath(os.getcwd()) | ||
| path = os.path.realpath(filename) | ||
| return os.path.commonpath((cwd, path)) == cwd | ||
| except (OSError, ValueError): | ||
| return False | ||
|
|
||
|
|
||
| def _sandbox_code_requested_import(): | ||
| try: | ||
| frame = sys._getframe(1) | ||
| except ValueError: | ||
| return True | ||
| while frame is not None: | ||
| module = frame.f_globals.get("__name__", "") | ||
| if ( | ||
| module == __name__ | ||
| or module == "importlib" | ||
| or module.startswith("importlib.") | ||
| or module.startswith("_frozen_importlib") | ||
| ): | ||
| frame = frame.f_back | ||
| continue | ||
| if module == "__main__" or _path_is_in_sandbox(frame.f_globals.get("__file__")): | ||
| return True | ||
| return False | ||
|
Comment on lines
+85
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When sandbox code loads a helper module from a writable path outside the current workdir, this check treats that helper as trusted library code and allows direct Useful? React with 👍 / 👎. |
||
| return True | ||
|
|
||
|
|
||
| def _blocked_network_module(fullname): | ||
| if not isinstance(fullname, str): | ||
| return None | ||
| root = fullname.split(".", 1)[0] | ||
| if root in _BLOCKED_NETWORK_MODULES: | ||
| return root | ||
| if root in _DIRECT_BLOCKED_NETWORK_MODULES and _sandbox_code_requested_import(): | ||
| return root | ||
|
Comment on lines
+97
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Fresh evidence in this revision is that Useful? React with 👍 / 👎. |
||
| return None | ||
|
|
||
|
|
||
| def _raise_blocked_network_module(root): | ||
| raise ModuleNotFoundError( | ||
| f"Blocked: low-level network module {root!r} is unavailable in sandboxed code" | ||
| ) | ||
|
|
||
|
|
||
| def _absolute_import_name( | ||
| name, | ||
| package = None, | ||
| level = 0, | ||
| ): | ||
| if not isinstance(name, str): | ||
| return name | ||
| if level: | ||
| if not isinstance(package, str) or not package: | ||
| return name | ||
| relative = "." * level + name | ||
| elif name.startswith(".") and isinstance(package, str) and package: | ||
| relative = name | ||
| else: | ||
| return name | ||
| try: | ||
| return importlib.util.resolve_name(relative, package) | ||
| except (ImportError, ValueError): | ||
| return name | ||
|
|
||
|
|
||
| def _guarded_import( | ||
| name, | ||
| globals = None, | ||
| locals = None, | ||
| fromlist = (), | ||
| level = 0, | ||
| ): | ||
| package = globals.get("__package__") if isinstance(globals, dict) else None | ||
| root = _blocked_network_module(_absolute_import_name(name, package, level)) | ||
| if root is not None: | ||
| _raise_blocked_network_module(root) | ||
| return _original_import(name, globals, locals, fromlist, level) | ||
|
|
||
|
|
||
| def _guarded_import_module(name, package = None): | ||
| root = _blocked_network_module(_absolute_import_name(name, package)) | ||
| if root is not None: | ||
| _raise_blocked_network_module(root) | ||
| return _original_import_module(name, package) | ||
|
|
||
|
|
||
| def _network_import_audit(event, args): | ||
| if event != "import" or not args: | ||
| return | ||
| root = _blocked_network_module(args[0]) | ||
| if root is not None: | ||
| _raise_blocked_network_module(root) | ||
|
|
||
|
|
||
| class _BlockedNetworkModuleFinder: | ||
| _unsloth_blocked_network_guard = True | ||
|
|
||
| def find_spec( | ||
| self, | ||
| fullname, | ||
| path = None, | ||
| target = None, | ||
| ): | ||
| root = _blocked_network_module(fullname) | ||
| if root is not None: | ||
| _raise_blocked_network_module(root) | ||
| return None | ||
|
|
||
|
|
||
| def _install_import_guard(): | ||
| global _import_guard_installed | ||
| if os.environ.get("UNSLOTH_STUDIO_SANDBOXED") != "1": | ||
| return | ||
| if not _import_guard_installed: | ||
| sys.addaudithook(_network_import_audit) | ||
| builtins.__import__ = _guarded_import | ||
| importlib.import_module = _guarded_import_module | ||
|
Comment on lines
+177
to
+180
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For sandboxed terminal commands that launch Python with Useful? React with 👍 / 👎. |
||
| _import_guard_installed = True | ||
| if any(getattr(finder, "_unsloth_blocked_network_guard", False) for finder in sys.meta_path): | ||
| return | ||
| sys.meta_path.insert(0, _BlockedNetworkModuleFinder()) | ||
|
|
||
|
|
||
| def _note(subject, original, mapped): | ||
|
|
@@ -307,6 +443,11 @@ def _path_mkdir(self, *args, **kwargs): | |
| pathlib.Path.mkdir = _path_mkdir | ||
|
|
||
|
|
||
| try: | ||
| _install_import_guard() | ||
| except Exception: # noqa: BLE001 - a broken guard must not break startup | ||
| pass | ||
|
|
||
| try: | ||
| _install() | ||
| except Exception: # noqa: BLE001 - a broken shim must never break user code | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The static policy now treats
httpcoreas a blocked low-level network module, but the runtime guard only deniesboto3/botocore. When the module name is built dynamically, e.g.name = ''.join(['http', 'core']); m = __import__(name); m.request('GET', 'https://evil/x'), the AST check cannot resolve the import and the sandbox-side guard lets it load, so an installedhttpcorecan still make arbitrary requests outside the host allowlist. Please keep this runtime denylist in sync with_SANDBOX_BLOCKED_NETWORK_MODULES.Useful? React with 👍 / 👎.