Created
January 8, 2026 16:43
-
-
Save sloonz/ef282a1f53366e1ed6f5cb848de015ba to your computer and use it in GitHub Desktop.
Sandboxing wrapper, merge variant. Context : https://gist.github.com/sloonz/4b7f5f575a96b6fe338534dbc2480a5d?permalink_comment_id=5926910#gistcomment-5926910
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: base | |
| clearenv: true | |
| dieWithParent: true | |
| unsharePid: true | |
| newSession: true | |
| mounts: | |
| /proc: proc | |
| /dev: dev | |
| /tmp: tmpfs | |
| /etc: {bind: {path: /etc, readOnly: true}} | |
| /usr: {bind: {path: /usr, readOnly: true}} | |
| /bin: {symlink: usr/bin} | |
| /sbin: {symlink: usr/sbin} | |
| /lib: {symlink: usr/lib} | |
| /lib64: {symlink: usr/lib} | |
| "~": tmpfs | |
| "~/.config": dir | |
| "~/.cache": dir | |
| "~/.local/share": dir | |
| "{env[XDG_RUNTIME_DIR]}": {tmpfs: {perms: 700}} | |
| /run/systemd/resolve: {bind: {}} | |
| env: | |
| PATH: true | |
| LANG: true | |
| XDG_RUNTIME_DIR: true | |
| XDG_SESSION_TYPE: true | |
| TERM: true | |
| HOME: true | |
| LOGNAME: true | |
| USER: true | |
| # CVE-2017-5226 prevention | |
| # f = seccomp.SyscallFilter(defaction = seccomp.ALLOW) | |
| # f.add_rule(seccomp.KILL_PROCESS, 'ioctl', seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCSTI)) | |
| # f.add_rule(seccomp.KILL_PROCESS, 'ioctl', seccomp.Arg(1, seccomp.MASKED_EQ, 0xffffffff, termios.TIOCLINUX)) | |
| # f.export_bpf(fd) | |
| seccomp: | |
| - arch: x86_64 | |
| data: |- | |
| IAAAAAQAAAAVAAAMPgAAwCAAAAAAAAAANQAAAQAAAEAVAAAJ/////xUAAAYQAAAAIAAAABwAAABU | |
| AAAAAAAAABUAAAMAAAAAIAAAABgAAAAVAAIAHFQAABUAAQASVAAABgAAAAAA/38GAAAAAAAAgAYA | |
| AAAAAAAA | |
| vars: | |
| flatpakInfo: |- | |
| [Application] | |
| name={name} | |
| [Instance] | |
| instance-id=bwrap-{pid} | |
| --- | |
| name: shell | |
| newSession: false | |
| env: | |
| LS_COLORS: true | |
| WORDCHARS: true | |
| EDITOR: true | |
| PYTHONIOENCODING: true | |
| PROMPT: true | |
| RPROMPT: true | |
| HISTFILE: true | |
| HISTSIZE: true | |
| SAVEHIST: true | |
| NODE_REPL_HISTORY: true | |
| SQLITE_HISTORY: true | |
| PYTHON_HISTORY: true | |
| MYSQL_HISTFILE: true | |
| mounts: | |
| "~/.zshrc": {bind: {readOnly: true}} | |
| "~/.config/git": {bind: {readOnly: true}} | |
| "~/.config/nvim": {bind: {readOnly: true}} | |
| --- | |
| name: display | |
| env: | |
| XKB_DEFAULT_LAYOUT: custom | |
| mounts: | |
| "~/.cache/mesa_shader_cache": {bind: {create: true}} | |
| "~/.cache/fontconfig": {bind: {create: true}} | |
| "~/.cache/radv_builtin_shaders": {bind: {create: true}} | |
| "~/.xkb": {bind: {readOnly: true}} | |
| --- | |
| name: x11 | |
| include: [display] | |
| env: | |
| DISPLAY: true | |
| mounts: | |
| /tmp/.X11-unix/X0: {bind: {readOnly: true}} | |
| --- | |
| name: wayland | |
| include: [display] | |
| env: | |
| WAYLAND_DISPLAY: true | |
| mounts: | |
| "{env[XDG_RUNTIME_DIR]}/{env[WAYLAND_DISPLAY]}": {bind: {readOnly: true}} | |
| --- | |
| name: audio | |
| mounts: | |
| "{env[XDG_RUNTIME_DIR]}/pulse/native": {bind: {readOnly: true}} | |
| "~/.config/pulse/cookie": {bind: {readOnly: true, try: true}} | |
| "{env[XDG_RUNTIME_DIR]}/pipewire-0": {bind: {readOnly: true}} | |
| --- | |
| name: drm | |
| mounts: | |
| /dev/dri: {bind: {dev: true}} | |
| /sys: {bind: {readOnly: true}} | |
| --- | |
| name: portal | |
| mounts: | |
| "{env[XDG_RUNTIME_DIR]}/doc": {bind: {path: "{env[XDG_RUNTIME_DIR]}/doc/by-app/{name}", create: true}} | |
| /.flatpak-info: {data: {content: "{flatpakInfo}", readOnly: true}} | |
| "{env[XDG_RUNTIME_DIR]}/flatpak-info": {data: {content: "{flatpakInfo}", readOnly: true}} | |
| dbus: | |
| sandbox: | |
| include: [base] | |
| asPid1: true | |
| mounts: | |
| /.flatpak-info: {data: {content: "{flatpakInfo}", readOnly: true}} | |
| "{env[XDG_RUNTIME_DIR]}/flatpak-info": {data: {content: "{flatpakInfo}", readOnly: true}} | |
| user: | |
| org.kde.*: own | |
| org.freedesktop.portal.*: talk | |
| org.freedesktop.Notifications: talk | |
| --- | |
| name: project | |
| include: [base, x11, wayland, rust, shell] | |
| chdir: "{env[HOME]}/workspace" | |
| env: | |
| VSCODE_EXTENSIONS: true | |
| vars: | |
| name: "{project}" | |
| projectRoot: "/opt/data/projects/{project}" | |
| mounts: | |
| "~": {bind: {path: "{projectRoot}/home", create: true}} | |
| "~/workspace": {bind: {path: "{projectRoot}/workspace", create: true}} | |
| "~/.cache": {bind: {path: "{projectRoot}/cache", create: true}} | |
| "{env[VSCODE_EXTENSIONS]}": {bind: {readOnly: true}} | |
| --- | |
| name: private-home | |
| include: [base] | |
| mounts: | |
| "~": {bind: {path: "~/.local/share/sandboxes/{name}", create: true}} | |
| "~/.config": {bind: {path: "~/.config/sandboxes/{name}", create: true}} | |
| "~/.cache": {bind: {path: "~/.cache/sandboxes/{name}", create: true}} | |
| --- | |
| name: cli | |
| include: [private-home, shell] | |
| matches: [node, npx, npm, yarn, go, tsc, cargo, rustup] | |
| vars: | |
| name: cli | |
| chdir: "{cwd}" | |
| mounts: | |
| "{cwd}": {bind: {}} | |
| --- | |
| name: none | |
| matches: [discor, gedit, foot, bnet, swtor, firefox, dolphin] | |
| disableSandbox: true | |
| --- | |
| name: default | |
| include: [private-home, x11, wayland, audio] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python | |
| import argparse | |
| import base64 | |
| import json | |
| import logging | |
| import os | |
| import pathlib | |
| import platform | |
| import pprint | |
| import re | |
| import shlex | |
| import sys | |
| import yaml | |
| sandboxes_cache = {} | |
| global_sandboxes = {} | |
| bwrap_flags = {"levelPrefix", "unshareAll", "shareNet", "unshareUser", "unshareUserTry", "unshareIpc", "unsharePid", "unshareNet", "unshareUts", "unshareCgroup", "unshareCgroupTry", "disableUserns", "assertUsernsDisabled", "clearenv", "newSession", "dieWithParent", "asPid1"} | |
| bwrap_options = {"argv0", "userns", "userns2", "pidns", "uid", "gid", "hostname", "chdir", "execLabel", "fileLabel", "seccomp", "syncFd", "blockFd", "usernsBlockFd", "infoFd", "jsonStatusFd"} | |
| bwrap_list_options = {"addSeccompFd", "capAdd", "capDrop", "lockFile", "remountRo"} | |
| merge_policy = { | |
| "items": {"mounts", "chmod"}, | |
| "literal": bwrap_flags.union(bwrap_options).union({"disableSandbox", "vars.*", "env.*", "dbus.sloppyNames.*", "dbus.sandbox.*", "dbus.user.*", "dbus.system.*"}), | |
| "list": bwrap_list_options.union({"extraArgs"}).union({"matches", "dbus.rules.*.*.*"}), | |
| "dict": {"vars", "env", "dbus", "dbus.sloppyNames", "dbus.sandbox", "dbus.user", "dbus.system", "dbus.rules.*", "dbus.rules.*.*"}, | |
| "discard": {"name", "include"}, | |
| } | |
| def tagged_append(tag, dest): | |
| class TaggedAppend(argparse.Action): | |
| def __call__(self, parser, ns, values, option_strings=None): | |
| dest.append((tag, values)) | |
| return TaggedAppend | |
| def load_sandboxes_file(path, default_name=None): | |
| logging.debug("loading %s", path) | |
| with open(path) as fd: | |
| sandboxes = list(yaml.safe_load_all(fd)) | |
| sandboxes = [sandboxes] if isinstance(sandboxes, dict) else sandboxes | |
| for i, sb in enumerate(sandboxes): | |
| if name := sb.get("name", default_name if i == 0 else None): | |
| sandboxes_cache[name] = sb | |
| return sandboxes | |
| def try_load_sandbox(name): | |
| if name in sandboxes_cache: | |
| return sandboxes_cache[name] | |
| if name in global_sandboxes: | |
| return global_sandboxes[name] | |
| candidate_paths = [ | |
| pathlib.Path(name), | |
| pathlib.Path(f"{name}.yml"), | |
| pathlib.Path(f"{name}.yaml"), | |
| pathlib.Path.home() / ".config" / "sandbox" / f"{name}.yml", | |
| pathlib.Path.home() / ".config" / "sandbox" / f"{name}.yaml", | |
| ] | |
| for p in candidate_paths: | |
| if p.exists(): | |
| return load_sandboxes_file(p, name)[0] | |
| def load_sandbox(name): | |
| sb = try_load_sandbox(name) | |
| if sb is None: | |
| raise Exception(f"sandbox not found: {name}") | |
| return sb | |
| def get_merge_policy(path, k): | |
| kl = ".".join(path + [k]) | |
| kg = ".".join(path + ["*"]) | |
| for p, s in merge_policy.items(): | |
| if kl in s: | |
| return p, path + [k] | |
| if kg in s: | |
| return p, path + ["*"] | |
| raise Exception(f"Unknown key while merging: {".".join(path + [k])}") | |
| def merge(a, b, path=[]): | |
| res = {} | |
| for k in set(a.keys()).union(b.keys()): | |
| policy, key_path = get_merge_policy(path, k) | |
| if policy == "list": | |
| res[k] = a.get(k, []) + b.get(k, []) | |
| elif policy == "items": | |
| left = a.get(k, {}) | |
| right = b.get(k, {}) | |
| res[k] = (list(left.items()) if isinstance(left, dict) else left) + \ | |
| (list(right.items()) if isinstance(right, dict) else right) | |
| elif policy == "dict": | |
| res[k] = merge(a.get(k, {}), b.get(k, {}), key_path) | |
| elif policy == "literal": | |
| res[k] = b.get(k, a.get(k, None)) | |
| return res | |
| def merge_sandboxes(sandboxes): | |
| def load_include(inc): | |
| inc = {"name": inc} if isinstance(inc, str) else inc | |
| if name := inc.get("name"): | |
| if inc.get("try"): | |
| return try_load_sandbox(name) | |
| else: | |
| return load_sandbox(name) | |
| elif path := inc.get("path"): | |
| if inc.get("try") and not os.path.exists(path): | |
| return {} | |
| else: | |
| return load_sandboxes_file(path)[0] | |
| else: | |
| assert False | |
| res = {} | |
| for sb in sandboxes: | |
| inc = merge_sandboxes(load_include(child_sb) for child_sb in sb.get("include", [])) | |
| res = merge(res, merge(inc, sb)) | |
| return res | |
| def get_sandbox(sb, default_vars): | |
| # Parse vars & env | |
| raw_vars = {**sb.get("vars", {})} | |
| raw_env = {} | |
| vars = {**default_vars} | |
| env = {} | |
| env_unset = set() | |
| for k, v in sb.get("env", {}).items(): | |
| if v is True: # inherit | |
| if k in os.environ: | |
| env[k] = os.environ[k] | |
| elif v is False: # clear | |
| env_unset.add(k) | |
| elif v is None: # nothing | |
| pass | |
| elif isinstance(v, str): | |
| raw_env[k] = v | |
| elif isinstance(v, dict): | |
| if "inherits" in v: | |
| if k in os.environ or "defaultValue" not in v: | |
| env[k] = os.environ[k] | |
| else: | |
| raw_env[k] = v["defaultValue"] | |
| elif "value" in v: | |
| if v.get("raw", False): | |
| env[k] = v["value"] | |
| else: | |
| raw_env[k] = v["value"] | |
| else: | |
| raise Exception(f"Invalid value for environment variable {k}: {repr(v)}") | |
| else: | |
| raise Exception(f"Invalid value for environment variable {k}: {repr(v)}") | |
| format_vars = {**vars, "env": {**os.environ, **env}} | |
| while True: | |
| changed = False | |
| for parsed, raw in ((vars, raw_vars), (env, raw_env)): | |
| for k in list(raw.keys()): | |
| try: | |
| parsed[k] = raw[k].format(**format_vars) | |
| del raw[k] | |
| changed = True | |
| except KeyError: | |
| pass | |
| format_vars = {**vars, "env": {**os.environ, **env}} | |
| if not raw_env and not raw_vars: | |
| break | |
| if not changed: | |
| assert False # circular definition | |
| # Parse mounts & chmod | |
| mounts = {os.path.expanduser(k.format(**format_vars).rstrip("/")): v for k, v in sb.get("mounts", []) if v} | |
| chmod = {os.path.expanduser(k.format(**format_vars).rstrip("/")): v for k, v in sb.get("chmod", []) if v} | |
| return {**sb, "vars": vars, "env": env, "envUnset": env_unset, "mounts": mounts, "chmod": chmod} | |
| def pipefd(data): | |
| pr, pw = os.pipe2(0) | |
| if os.fork() == 0: | |
| os.close(pr) | |
| os.write(pw, data) | |
| sys.exit(0) | |
| else: | |
| os.close(pw) | |
| return pr | |
| def get_bwrap_args(sb): | |
| def bwrap_name(name): | |
| if name == "argv0": | |
| return name | |
| return re.sub(r"(?<=[a-z])([A-Z0-9+])", lambda m: "-" + m.group(1).lower(), name) | |
| def format_seccomp_value(value): | |
| if isinstance(value, dict): # {data: string, arch: string} | |
| value = [value] | |
| if isinstance(value, list): # {data: string, arch: string}[] | |
| data = [base64.b64decode(prog["data"]) for prog in value if prog["arch"] == platform.machine()] | |
| if len(data) == 0: | |
| raise Exception(f"seccomp program not found for that architecture: {platform.machine()}") | |
| return str(pipefd(data[0])) | |
| else: # fd | |
| return str(value) | |
| def format_option_value(name, value): | |
| if name in ("seccomp", "addSeccomp"): | |
| return format_seccomp_value(value) | |
| elif name in ("userns", "userns2", "pidns","syncFd", "blockFd", "userNsBlockFd", "infoFd", "jsonStatusFd"): | |
| return str(value) | |
| else: | |
| return str(value).format(**format_vars) | |
| def format_datasource_value(value): # {fd: number} | {content: string, raw?: boolean, base64?: boolean} | |
| if (fd := value.get("fd")) is not None: | |
| return str(fd) | |
| elif (content := value.get("content")) is not None: | |
| if not value.get("raw"): | |
| content = content.format(**format_vars) | |
| if value.get("base64"): | |
| content = base64.b64decode(content) | |
| else: | |
| content = content.encode("utf-8") | |
| return str(pipefd(content)) | |
| else: | |
| raise NotImplementedError | |
| format_vars = {**sb["vars"], "env": {**os.environ, **sb["env"]}} | |
| args = [f"--{bwrap_name(f)}" for f in bwrap_flags if sb.get(f)] | |
| for o in bwrap_options: | |
| if sb.get(o) not in (False, None): | |
| args.extend((f"--{bwrap_name(o)}", format_option_value(o, sb[o]))) | |
| for o in bwrap_list_options: | |
| for v in sb.get(o, []): | |
| if v not in (False, None): | |
| args.extend((f"--{bwrap_name(o)}", format_option_value(o, v))) | |
| args.extend(arg.format(**format_vars) for arg in sb.get("extraArgs", [])) | |
| for e in sb["envUnset"]: | |
| args.extend(("--unsetenv", e)) | |
| for k, v in sb["env"].items(): | |
| args.extend(("--setenv", k, v)) | |
| for dest_path, mount in sorted(sb["mounts"].items()): | |
| if mount in ("proc", "dev", "tmpfs", "mqueue", "dir"): | |
| args.extend((f"--{mount}", dest_path.format(**format_vars))) | |
| elif isinstance(mount, dict): | |
| if (tmpfs := mount.get("tmpfs")) is not None: # { tmpfs: { perms?: number; size?: number }} | |
| if (perms := tmpfs.get("perms")) is not None: | |
| args.extend(("--perms", str(perms))) | |
| if (size := tmpfs.get("size")) is not None: | |
| args.extend(("--size", str(size))) | |
| args.extend(("--tmpfs", dest_path)) | |
| elif (dir := mount.get("dir")) is not None: # { dir: { perms?: number }} | |
| if (perms := dir.get("perms")) is not None: | |
| args.extend(("--perms", str(perms))) | |
| args.extend(("--dir", dest_path)) | |
| elif (symlink := mount.get("symlink")) is not None: # { symlink: string } | |
| args.extend(("--symlink", symlink.format(**format_vars), dest_path)) | |
| elif (bind := mount.get("bind")) is not None: # { bind: { path: string; readOnly?: boolean; dev?: boolean; try?: boolean, create?: boolean }} | |
| prefix = "dev-" if bind.get("dev") else "ro-" if bind.get("ro") else "" | |
| suffix = "-try" if bind.get("try") else "" | |
| src_path = os.path.expanduser(bind.get("path", dest_path).format(**format_vars)) | |
| if bind.get("create"): | |
| os.makedirs(src_path, exist_ok=True) | |
| args.extend(("--" + prefix + "bind" + suffix, src_path, dest_path)) | |
| elif (fd := mount.get("fd")) is not None: # { fd: { fd: number; readOnly?: boolean }} | |
| args.extend(("--ro-bind-fd" if fd.get("readOnly") else "--bind-fd", str(fd["fd"]))) | |
| elif (file := mount.get("file")) is not None: # { file: DataSource & { perms?: number }} | |
| if (perms := file.get("perms")) is not None: | |
| args.extend(("--perms", str(perms))) | |
| args.extend(("--file", format_datasource_value(file), dest_path)) | |
| elif (data := mount.get("data")) is not None: # { data: DataSource & { readOnly?: boolean; perms?: number }} | |
| if (perms := data.get("perms")) is not None: | |
| args.extend(("--perms", str(perms))) | |
| args.extend(("--ro-bind-data" if data.get("readOnly") else "--bind-data", format_datasource_value(data), dest_path)) | |
| elif (overlay := mount.get("overlay")) is not None: # { overlay: { lower: string[]; upper?: string; work?: string; mode?: "rw" | "tmp" | "ro" }} | |
| for lower in overlay["lower"]: | |
| args.extend(("--overlay-src", lower.format(**format_vars))) | |
| mode = overlay.get("mode", "rw" if "upper" in overlay and "work" in overlay else "tmp") | |
| if mode == "rw": | |
| args.extend(("--overlay", overlay["upper"], overlay["work"], dest_path)) | |
| else: | |
| args.extend((f"--{mode}-overlay", dest_path)) | |
| else: | |
| raise Exception(f"invalid mount value: {repr(mount)}") | |
| else: | |
| raise Exception(f"invalid mount value: {repr(mount)}") | |
| for path, mode in sorted(sb["chmod"]): | |
| args.extend(("--chmod", path.format(**format_vars), str(mode))) | |
| return args | |
| def get_dbus_proxy_args(sb, bus): | |
| args = [] | |
| if sb.get("dbus", {}).get("sloppyNames", {}).get(bus, False): | |
| args.append("--sloppy-names") | |
| for name, policy in sb.get("dbus", {}).get(bus, {}).items(): | |
| args.append(f"--{policy}={name}") | |
| for rule_type in ("broadcast", "call"): | |
| for name, rules in sb.get("dbus", {}).get("rules", {}).get(bus, {}).get(rule_type, {}).keys(): | |
| args.extend(f"--broadcast={name}={rule}" for rule in rules) | |
| return args | |
| def setup_dbus_proxy(sb, vars, proxy_dir, buses): | |
| args = [] | |
| proxy_bwrap_args = ["--bind", proxy_dir, proxy_dir] | |
| cmd_bwrap_args = [] | |
| for bus, address, addr_env in buses: | |
| if bus_args := get_dbus_proxy_args(sb, bus): | |
| args.extend((address, f"{proxy_dir}/{bus}", "--filter")) | |
| args.extend(bus_args) | |
| addr_path = address.removeprefix("unix:path=") | |
| proxy_bwrap_args.extend(("--bind", addr_path, addr_path)) | |
| cmd_bwrap_args.extend(("--bind", f"{proxy_dir}/{bus}", addr_path)) | |
| if addr_env: | |
| cmd_bwrap_args.extend(("--setenv", addr_env, address)) | |
| if not args: | |
| return [] | |
| pr, pw = os.pipe2(0) | |
| os.makedirs(proxy_dir, exist_ok=True) | |
| args = ["xdg-dbus-proxy", f"--fd={pw}"] + args | |
| logging.debug("proxy args for dbus proxy: %r", shlex.join(args)) | |
| if proxy_sb := sb.get("dbus", {}).get("sandbox"): | |
| proxy_sb = get_sandbox(merge_sandboxes([proxy_sb]), vars) | |
| debug_object("dbus proxy sandbox", proxy_sb) | |
| proxy_bwrap_args = get_bwrap_args(proxy_sb) + proxy_bwrap_args | |
| args = ["bwrap", "--args", str(pipefd("\0".join(bwrap_args).encode("utf-8")))] + args | |
| logging.debug("bwrap args for xdg-dbus-proxy for bus: %s", shlex.join(proxy_bwrap_args)) | |
| if os.fork() == 0: | |
| os.close(pr) | |
| os.execlp(args[0], *args) | |
| else: | |
| os.close(pw) | |
| assert os.read(pr, 1) == b"x" | |
| return ["--sync-fd", str(pr)] + cmd_bwrap_args | |
| def debug_object(label, obj): | |
| if logging.getLogger().isEnabledFor(logging.DEBUG): | |
| logging.debug("%s:\n%s", label, pprint.pformat(obj)) | |
| configs_sources = [] | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("-l", "--log-level", choices=[lvl.lower() for lvl in ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG")]) | |
| parser.add_argument("-n", "--name", action=tagged_append("name", configs_sources)) | |
| parser.add_argument("-j", "--json", action=tagged_append("json", configs_sources)) | |
| parser.add_argument("-f", "--file", action=tagged_append("file", configs_sources)) | |
| parser.add_argument("-s", "--set", action=tagged_append("set", configs_sources)) | |
| parser.add_argument("-a", "--autoload", action="store_true") | |
| parser.add_argument("-M", "--no-match", action="store_true") | |
| parser.add_argument("-D", "--no-default", action="store_true") | |
| parser.add_argument("--default-sandbox", action="store", default="default") | |
| parser.add_argument("executable", nargs="?") | |
| parser.add_argument("args", nargs=argparse.REMAINDER) | |
| args = parser.parse_args() | |
| executable_name = os.path.basename(args.executable or os.environ.get("SHELL", "sh")) | |
| if args.log_level: | |
| logging.getLogger().setLevel(getattr(logging, args.log_level.upper())) | |
| for global_path in (pathlib.Path.home() / ".config" / "sandbox.yaml", pathlib.Path.home() / ".config" / "sandbox.yml"): | |
| if global_path.exists(): | |
| global_sandboxes = load_sandboxes_file(global_path) | |
| configs = [] | |
| for source_type, source_data in configs_sources: | |
| if source_type == "name": | |
| for name in source_data.split(","): | |
| configs.append(load_sandbox(name.strip())) | |
| elif source_type == "json": | |
| configs.append(json.loads(source_data)) | |
| elif source_type == "file": | |
| configs.extend(load_sandboxes_file(source_data)) | |
| elif source_type == "set": | |
| k, v = source_data.split("=", 1) | |
| k = re.sub(r"^\$", "env.", re.sub("^:", "vars.", k)) | |
| v = json.loads(v) if v and (v[0] in '"[{'or v in ("true", "false", "null")) else v | |
| obj = {} | |
| cur = obj | |
| for is_array, p, end in re.findall(r'(?:^|\.)(@?)("[^"=]+"|[^".=]+)(=$)?', f"{k}="): | |
| p = p[1:-1] if p.startswith('"') else p | |
| c = v if end else {} | |
| if is_array: | |
| cur[p] = [c] | |
| cur = cur[p][0] | |
| else: | |
| cur[p] = c | |
| cur = cur[p] | |
| configs.append(obj) | |
| else: | |
| raise NotImplementedError | |
| if args.autoload: | |
| sb = try_load_sandbox(executable_name) | |
| if sb and sb not in configs: | |
| configs.append(sb) | |
| if not args.no_match: | |
| for sb in global_sandboxes: | |
| if executable_name in sb.get("matches", []) and sb not in configs: | |
| configs.append(sb) | |
| if not configs and not args.no_default: | |
| configs = [load_sandbox(args.default_sandbox)] | |
| debug_object("configs", configs) | |
| default_vars = { | |
| "pid": os.getpid(), | |
| "cwd": os.getcwd(), | |
| "executable": args.executable, | |
| "name": executable_name, | |
| } | |
| sb = get_sandbox(merge_sandboxes(configs), default_vars) | |
| debug_object("sandbox", sb) | |
| if sb.get("disableSandbox"): | |
| os.execlp(args.executable, args.executable, *args.args) | |
| dbus_proxy_dir = f"{os.environ["XDG_RUNTIME_DIR"]}/xdg-dbus-proxy/bwrap-{os.getpid()}" | |
| dbus_proxy_args = setup_dbus_proxy(sb, default_vars, dbus_proxy_dir, ( | |
| ("system", "unix:path=/run/dbus/system_bus_socket", None), | |
| ("user", os.environ["DBUS_SESSION_BUS_ADDRESS"], "DBUS_SESSION_BUS_ADDRESS"), | |
| )) | |
| bwrap_args = get_bwrap_args(sb) | |
| bwrap_args.extend(dbus_proxy_args) | |
| logging.debug("bwrap command: %s", shlex.join(["bwrap"] + bwrap_args + [args.executable] + args.args)) | |
| os.execlp("bwrap", "bwrap", "--args", str(pipefd("\0".join(bwrap_args).encode("utf-8"))), args.executable or executable_name, *args.args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment