{"id":45041411,"url":"https://github.com/dtxdf/x11appjail","last_synced_at":"2026-05-30T22:00:42.924Z","repository":{"id":336325132,"uuid":"1149220801","full_name":"DtxdF/x11appjail","owner":"DtxdF","description":"x11 applications already sandboxed by AppJail","archived":false,"fork":false,"pushed_at":"2026-05-26T00:07:24.000Z","size":2012,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-26T02:33:39.927Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DtxdF.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-03T21:32:06.000Z","updated_at":"2026-05-26T00:07:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/DtxdF/x11appjail","commit_stats":null,"previous_names":["dtxdf/x11appjail"],"tags_count":39,"template":false,"template_full_name":null,"purl":"pkg:github/DtxdF/x11appjail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DtxdF%2Fx11appjail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DtxdF%2Fx11appjail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DtxdF%2Fx11appjail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DtxdF%2Fx11appjail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DtxdF","download_url":"https://codeload.github.com/DtxdF/x11appjail/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DtxdF%2Fx11appjail/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33711018,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-19T07:27:40.850Z","updated_at":"2026-05-30T22:00:42.902Z","avatar_url":"https://github.com/DtxdF.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# x11appjail: x11 applications already sandboxed by AppJail\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/x11appjail.png\" /\u003e\n\u003c/p\u003e\n\nOS-level virtualization is not as perfect as hardware-level virtualization. Containers run the same kernel as the host, and in most cases, if an application needs a file, a directory, or a device, these resources must be shared; therefore, this trade-off must be accepted. A vulnerability in a device (`/dev`), even if the application is running inside the container as a non-root user, could pose a risk to the host. However, all of this applies in the same way as if an application were running from the host, and even worse, since the application has more privileges. However, when implemented correctly, a containerized application is far superior, in terms of isolation, to one running from the host. You can, for example, limit the scope of devices in `/dev`, restrict the connections an application can establish, set resource limits, isolate the filesystem and processes, and much more; all in a compartmentalized manner. This means that if you want to run a web browser in a container, the fact that one is compromised does not imply that another container running your email client is at the same risk.\n\nIn FreeBSD, OS-level virtualization is implemented using jails, but most users prefer to use a jail manager. In our case, we use AppJail from this repository because of its flexibility and because it can safely run X11 applications thanks to `appjail-x11(1)`. See [Sandboxed x11 applications on AppJail Handbook](https://appjail.readthedocs.io/en/latest/x11/#sandboxed-x11-applications) for details.\n\n---\n\nTable of Contents\n=================\n\n* [x11appjail: x11 applications already sandboxed by AppJail](#x11appjail-x11-applications-already-sandboxed-by-appjail)\n* [Table of Contents](#table-of-contents)\n   * [Prerequisites](#prerequisites)\n      * [AppJail configuration](#appjail-configuration)\n      * [Privileges](#privileges)\n         * [Privileges for AppJail](#privileges-for-appjail)\n         * [Privileges for pkg(8)](#privileges-for-pkg8)\n   * [How to use this repository](#how-to-use-this-repository)\n      * [Environment](#environment)\n      * [Ephemeral Jails](#ephemeral-jails)\n      * [Developing a new AppScript](#developing-a-new-appscript)\n      * [App Configuration](#app-configuration)\n      * [Scripts](#scripts)\n   * [Sandboxed Applications](#sandboxed-applications)\n   * [Tips \u0026amp;\u0026amp; Tricks](#tips--tricks)\n      * [Using a command-line snippet manager](#using-a-command-line-snippet-manager)\n      * [Keyboard Layout](#keyboard-layout)\n      * [Changing escape key](#changing-escape-key)\n      * [Closing an application](#closing-an-application)\n      * [Icons and Cache (in XFCE)](#icons-and-cache-in-xfce)\n      * [Sharing files between jails](#sharing-files-between-jails)\n      * [Clipboard](#clipboard)\n         * [Analyzing the clipboard for malicious or misleading unicode characters](#analyzing-the-clipboard-for-malicious-or-misleading-unicode-characters)\n         * [Transferring a file](#transferring-a-file)\n         * [Synchronizing the clipboard between two X servers](#synchronizing-the-clipboard-between-two-x-servers)\n      * [Opening an image in a jail](#opening-an-image-in-a-jail)\n      * [Opening a PDF in a jail](#opening-a-pdf-in-a-jail)\n      * [Opening URLs](#opening-urls)\n      * [Receiving notifications](#receiving-notifications)\n   * [Demo](#demo)\n\n## Prerequisites\n\n### AppJail configuration\n\nThese scripts are designed so that users don't have to do much to get started, but at the very least, before running it, you should have preconfigured the parameters related to ZFS (if you use it) and, as a recommendation, `EXT_IF`. See `appjail.conf(5)` and the [ZFS on AppJail Handbook](https://appjail.readthedocs.io/en/latest/ZFS/) for more details.\n\n### Privileges\n\n#### Privileges for AppJail\n\nAppJail requires privileges to run, but it can be integrated with tools such as [security/doas](https://freshports.org/security/doas) to run it as a user without root privileges. This is recommended when you are the only person using the computer and have privileges, or in cases where there are more than two sysadmins or developers on the same server with root access.\n\n**/usr/local/etc/doas.conf**:\n\n```\npermit nopass keepenv :appjail as root cmd appjail\n```\n\nThis rule also assumes that you have a group named appjail. If you don't, don't worry:\n\n```\npw groupadd -n appjail\n```\n\nTo add your user to the `appjail` group simply run the following:\n\n```\npw groupmod -n appjail -m \"$USER\"\n```\n\nWhere `$USER` is your user. For these changes to take effect, you must log back into the system if you are adding yourself.\n\nTo test the changes above, simply run the following as a non-root user:\n\n```\nappjail help\n```\n\nSee also: [Trusted Users on AppJail Handbook](https://appjail.readthedocs.io/en/latest/trusted-users/).\n\n#### Privileges for pkg(8)\n\nThese AppScripts will check whether the dependencies are available on your system and, if not, install them. This requires privileges, so let’s configure `doas.conf(5)` based on what we’ve done previously.\n\n```\npermit nopass :appjail as root cmd pkg\n```\n\nThe dependencies that will be installed:\n\n* `appjail`\n* `su-exec`\n* `xauth`\n* `xdotool`\n* `xev`\n* `xephyr`\n* `xseticon`\n* `git-tiny`\n* `debootstrap` (LinuxJails-only)\n\n## How to use this repository\n\n[AppScript](https://freshports.org/archivers/appscript) is used to package each application into its own executable. You can run these AppScripts in a portable manner or install them. Note that if you choose to run an AppScript without installing it, environment variables are not preserved, which means you must define each environment variable before running the AppScript. If you choose to install an AppScript, only environment variables prefixed with `X11APPJAIL_` and defined before running the AppScript are preserved, and the user can override them simply by defining them before running the installed AppScript.\n\nYou can run an application from this repository by cloning it and executing the `./\u003cAPP\u003e/APPSCRIPT` script, which is simply a POSIX shell script. Note that you cannot install an application this way.\n\n```sh\ngit clone https://github.com/DtxdF/x11appjail.git\ncd ./x11appjail/\n./\u003cAPP\u003e/APPSCRIPT\n```\n\nHowever, the best approach is to create an AppScript. To create an AppScript, this repository contains a Makefile that automates this step.\n\n```\nmake build APP=\u003cAPP\u003e\n# or to build everything:\nmake -j$(nproc) build-all\n```\n\nIf you don't feel like building AppScripts or don't want to clone this repository, assets are automatically created by each release. Regardless of the path you choose, all AppScripts run in the same way: like any other executable file.\n\n### Environment\n\nAll AppScripts support the following environment variables:\n\n* `X11APPJAIL_JAIL`: Environment variable used by some scripts. This environment variable is always set by each AppScript and cannot be modified. The syntax used by these AppScripts is `x11appjail-\u003cAPPNAME\u003e-\u003cUID\u003e_${X11APPJAIL_PROFILE}`, where `\u003cAPPNAME\u003e` is a hardcoded name set by each AppScript in its own `app.conf` and `\u003cUID\u003e` is determined based on `id -u`.\n* `X11APPJAIL_PROFILE` (default: `default`): AppScripts will attempt to reuse the jail, and the jail name is based on this parameter. This parameter is used to create multiple instances of the same application. The cache and data are not shared with other instances. This is useful for isolating tasks within each jail. This environment variable must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*$` to be considered valid.\n* `X11APPJAIL_LOCKDIR` (default: `${HOME}/x11appjail/locks/${X11APPJAIL_JAIL}`): Location of locks to prevent race conditions and unwanted effects in some operations.\n* `X11APPJAIL_APPSDIR` (default: `${HOME}/x11appjail/apps`): Root directory of `X11APPJAIL_APPDIR`.\n* `X11APPJAIL_APPDIR` (default: `${X11APPJAIL_APPSDIR}/${X11APPJAIL_JAIL}`): Directory used by installed AppScripts. Two scripts are created: `START` and `APPSCRIPT`. `START` is simply a wrapper for `APPSCRIPT` that preserves the environment variables used during AppScript installation. `APPSCRIPT` is a copy of the executed AppScript. When `START` is executed, this script will honor all environment variables passed to it, even if they are already defined.\n* `X11APPJAIL_DATA` (default: `${HOME}/x11appjail/data`): Root directory of `X11APPJAIL_DATADIR`.\n* `X11APPJAIL_DATADIR` (default: `${X11APPJAIL_DATA}/${JAIL}`): The data that must persist. The owner and group of each file will match those of the user running the AppScript.\n* `X11APPJAIL_CACHEDIR` (default: `${HOME}/x11appjail/cache/${JAIL}`): Another directory that the jail uses to cache data, so that recreation is faster.\n* `X11APPJAIL_OSVERSION` (optional): Configure the `osversion` parameter of `appjail-quick(1)`. By default, this value is calculated based on the kernel version. Note that this AppScript will create the release directory using distfiles if your host has a kernel version lower than `1500000`; otherwise, `pkgbase(8)` will be used. This parameter affects the release created by `appjail-fetch(1)`. Ignored when the AppScript installs a LinuxJail.\n* `X11APPJAIL_VIRTUALNET` (optional): When set, instead of inheriting the host's network stack, a virtual network specified by this environment variable is used. This requires you to configure a few more things, if you haven't already. See [Packet Filter on AppJail Handbook](https://appjail.readthedocs.io/en/latest/networking/packet-filter/). Ignored when the AppScript installs a LinuxJail.\n* `X11APPJAIL_INSTALL` (optional): If set (to any value such as `1`), the AppScript will only install itself and the .desktop file within `~/.local/share/applications`. The icon is installed separately, but this depends on the specific application. The .desktop file isn't included in the AppScript; instead, it is copied directly from the jail and modified. The `Exec` entry is modified to use the `START` script mentioned earlier, and `Name` is simply suffixed with the string `(AppJail)`.\n* `X11APPJAIL_UNINSTALL` (optional): If set, the AppScript will delete any files during the installation phase and stop the jail.\n* `X11APPJAIL_LABEL[0-9]+` (optional): Specify `appjail-label(1)` labels to be assigned to the jail. At a minimum, you must start with `X11APPJAIL_LABEL0`.\n* `X11APPJAIL_PKG_CONF` (optional): Copy a `pkg.conf(5)` from the host as `/usr/local/etc/pkg/repos/FreeBSD.conf` inside the jail. Ignored when the AppScript installs a LinuxJail.\n* `X11APPJAIL_EXEC_TOOL` (default: `doas`): Tool for elevating privileges in order to install dependencies.\n* `X11APPJAIL_SHAREDIR` (default: `${HOME}/x11appjail/share/${X11APPJAIL_JAIL}`): Miscellaneous files that the AppScript can use, such as icons.\n* `X11APPJAIL_ALLOW_HOST` (optional): Create a cookie after creating the jail and before starting the X server. Useful for clipboard access. See [Sandboxed x11 applications/Clipboard on AppJail Handbook](https://appjail.readthedocs.io/en/latest/x11/#clipboard).\n* `X11APPJAIL_WRAPPER` (optional): When this environment variable is set, the user can specify an executable file that the AppScript will run instead of the one specified by the creator. See also `wrapper.sh` script for environment variables used by this script.\n* `X11APPJAIL_NO_EPHEMERAL` (optional): By default, unless otherwise specified, all jails are ephemeral. After system startup, the `appjail(1)`'s `rc(8)` script (or you yourself, implicitly when executing the `Exec` entry of the .desktop file) will start the jail, and AppJail will detect that the jail is ephemeral, so it will be removed. AppJail will detect this and recreate the jail. The advantage of this is that you'll get automatic updates, but the downside is that it will initially slow down the jail's startup time depending on your system specs. With this environment variable, you can make your jail permanent.\n* `X11APPJAIL_TITLE` (optional): Define a custom title. If none is specified, the app description will be used. Note that the title will be concatenated to the profile name. For example, if you use the profile `default` and the title `foobar`, the resulting title will be `default: foobar`.\n* `X11APPJAIL_DEBUG` (optional): Turns on debug, which is equivalent to `set -x` in every `sh(1)` script.\n* `X11APPJAIL_SERVICE` (optional): Which service to run. See [Services](Services/README.md) for details.\n* `X11APPJAIL_SERVICE_FROM` (optional): Where this service will be provided. See [Services](Services/README.md) for details.\n* `X11APPJAIL_OCI_FROM` (optional): Use an OCI image. This is useful for customizing the jail in ways not intended by default. This has the good side-effect that `appjail-fetch(1)` isn't called, since the OCI image is supposed to contain all the packages needed for the AppScript’s Makejail that you want to use, and each Makejail will only call `appjail-pkg(1)` if any packages are missing. This cannot be used with LinuxJails.\n* `X11APPJAIL_OCI_ARGS` (optional): See `container` option in `appjail-quick(1)`.\n\nIn addition to the environment variables mentioned, `USER`, `HOME`, and `XAUTHORITY` can affect the execution of each AppScript. And keep in mind that each AppScript may need or use custom environment variables.\n\n### Ephemeral Jails\n\nEach jail created is set with the `ephemeral` option of `appjail-quick(1)`, which means that when it stops, it is simply destroyed. Don't worry, the volumes are mounted on your host, so any data that needs to persist will be there.\n\n### Developing a new AppScript\n\n### App Configuration\n\nThis file is used to define the parameters used by each AppScript and is self-explanatory.\n\n* `APPNAME`\n* `APPDESCR`\n* `APPBIN`\n* `LINUX_VERSION`\n* `NO_EPHEMERAL`\n\n### Scripts\n\nThese scripts are designed to be as generic as possible, and although they are located in the root tree of this repository, they are symlinks in each AppScript's directory to save space. When an AppScript is created using `make build` (or `make build-all`), they are resolved using `appscript(1)`'s `-L` flag.\n\n* `default.conf`\n* `APPSCRIPT`\n* `create.sh`\n* `startup.sh`\n* `start-server.sh`\n* `Services/`\n\nIn addition to these scripts, there are scripts that can be defined in each application and that only affect the specified application:\n\n* `install.sh` (mandatory): A script that installs any necessary files when `X11APPJAIL_INSTALL` is set. The files that need to be installed include, at a minimum, the .desktop file, preferably copied from the jail to ensure you have the most up-to-date version.\n* `uninstall.sh` (mandatory): Delete any files installed during the installation phase.\n* `arguments.sh` (optional): Additional arguments passed to `appjail-makejail(1)`. The `set` command of `sh(1)` is used to replace arguments, so you must define each new parameter using the following syntax: `set -- \"$@\" \u003cnew argument\u003e`. After this file is processed, `--puid`, `--pgid` and, depending on `X11APPJAIL_PKG_CONF` environment variable, `--pkg_conf` are passed to the Makejail, so you must end your file with at least `set -- \"$@\" --`.\n* `wrapper.sh` (optional): By default, if no `wrapper.sh` script exists and the `X11APPJAIL_WRAPPER` environment variable isn't specified, the `exec appjail cmd jexec \"${X11APPJAIL_JAIL}\" -U noroot -e DISPLAY=\":${X11APPJAIL_DISPLAY}\" \"${X11APPJAIL_APPBIN}\" \"$@\"` command is executed. However, you can create a `wrapper.sh` script with the execute bit set within the AppScript's directory to perform actions that the default command does not. The environment variable `X11APPJAIL_DISPLAY` (without the `:` prefix) is set to the display that the process pointed to by the `X11APPJAIL_APPBIN` environment variable should have access to.\n\n## Sandboxed Applications\n\n* [Nanonote](Apps/nanonote/README.md): Minimalist note taking application.\n* [LibreWolf](Apps/librewolf/README.md): Custom version of Firefox, focused on privacy, security and freedom.\n* [Tor Browser](Apps/tor-browser/README.md): Tor Browser for FreeBSD.\n* [Telegram Desktop](Apps/telegram-desktop/README.md): Telegram Desktop messaging app.\n* [Chromium](Apps/chromium/README.md): Google web browser based on WebKit.\n* [Brave](Apps/brave/README.md): Brave web browser based on WebKit.\n* [Firefox](Apps/firefox/README.md): Web browser based on the browser portion of Mozilla.\n* [Thunderbird](Apps/thunderbird/README.md): Mozilla Thunderbird is standalone mail and news that stands above.\n* [Claws Mail](Apps/claws-mail/README.md): Lightweight and featureful GTK based e-mail and news client.\n* [Feh](Apps/feh/README.md): Image viewer that utilizes Imlib2.\n* [Evince](Apps/evince/README.md): GNOME multi-format document viewer without GNOME dependencies.\n* [Badwolf](Apps/badwolf/README.md): Minimalist and privacy-oriented WebKitGTK browser.\n* [VLC](Apps/vlc/README.md): Qt based multimedia player and streaming server.\n\n## Tips \u0026\u0026 Tricks\n\n### Using a command-line snippet manager\n\nKeeping track of which environment variables you've used isn't very practical when installing multiple AppScripts, so it's a good idea to use a snippet manager like [deskutils/pet](https://freshports.org/deskutils/pet).\n\n```sh\npkg install -y pet fzf\n```\n\n**~/.config/pet/snippet.toml**:\n\n```toml\n[[Snippets]]\n  Description = \"Install LibreWolf in a jail\"\n  Output = \"\"\n  Tag = [\"librewolf\"]\n  command = \"mkdir -p \\\"${HOME}/AppScripts\\\" \u0026\u0026 fetch -qo \\\"${HOME}/AppScripts/librewolf.appscript\\\" https://github.com/DtxdF/x11appjail/releases/latest/download/librewolf-amd64.appscript \u0026\u0026 chmod +x \\\"${HOME}/AppScripts/librewolf.appscript\\\" \u0026\u0026 env X11APPJAIL_INSTALL=1 X11APPJAIL_ALLOW_HOST=1 X11APPJAIL_ENABLE_SOUND=1 \\\"${HOME}/AppScripts/librewolf.appscript\\\"\"\n```\n\nThen execute the snippet.\n\n```\npet exec -t librewolf\n```\n\nEven if the current binary is overwritten, the jail isn't recreated unless it does not exist or the AppScript detects that the checksum is different (as defined in the `x11appjail.checksum` label created using `appjail-label(1)`), so you can simply rerun the snippet to easily update the jail.\n\n### Keyboard Layout\n\n[x11/setxkbmap](https://freshports.org/x11/setxkbmap) is installed by the Makejails, so you can change the keyboard layout.\n\nAt runtime, you can press `C-t :`, and then run `exec setxkbmap \u003clayout\u003e`. And if you want the changes to persist, add that command to your `~/x11appjail/data/\u003cjail\u003e/.ratpoisonrc` file.\n\nIf you have allowed the host to access the X server created by `Xephyr(1)` using the `X11APPJAIL_ALLOW_HOST` environment variable, you can also use the host's `setxkbmap(1)` to change the keyboard layout from the host.\n\n### Changing escape key\n\n`ratpoison(1)`'s escape key may conflict with applications such as web browsers, so it's best to change it.\n\n**~/x11appjail/data/`\u003cjail\u003e`/.ratpoisonrc**:\n\n```\nescape C-e\n```\n\n### Closing an application\n\nRemember that closing `Xephyr(1)`'s window may force your application to close, which may not be desirable. First close the application and then close the window manager. For `ratpoison(1)`, just press `C-t k` to close the current window and then `C-t :` to execute the `quit` command.\n\n### Icons and Cache (in XFCE)\n\nSometimes, XFCE doesn't display icons correctly in Applications Menu. A simple workaround is to just run `xfce4-panel -r`.\n\n### Sharing files between jails\n\nA compromised jail can inject a backdoor into any file created within it, or the file itself may be malicious; however, sometimes it is necessary to share a file with another jail. There is no specific protocol for this, but there is a much simpler way to accomplish this job: by using a file manager.\n\n```sh\nthunar ~/x11appjail/data\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-1.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-2.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-3.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-4.png\" /\u003e\n\u003c/p\u003e\n\n**TIP**: Create a bookmark using CTRL-D, and then give it a meaningful name. In my case, for example, Telegram and Chromium, which are apps I use frequently.\n\n### Clipboard\n\n**Note**: The following assumes that your user has the necessary cookies to access the X servers. See `X11APPJAIL_ALLOW_HOST` environment variable.\n\nWhen working with the clipboard, there are two approaches to consider. The first involves simply reading the clipboard from a single X server, which applies to some applications we'll discuss later. The second involves synchronizing the clipboard between two X servers, which is likely the most common scenario, though also the least secure.\n\nRegardless of which method you choose, you need to know which display server the jailed X11 application uses:\n\n```console\n$ appjail jail list -j x11appjail-chromium-15000_default x11_display\nX11_DISPLAY\n1000\n```\n\n#### Analyzing the clipboard for malicious or misleading unicode characters\n\nUsing [unicode-show](https://freshports.org/security/py-unicode-show) and [xclip](https://freshports.org/x11/xclip), we can analyze the data to detect potential malicious code, such as this \"[trojan](https://github.com/nickboucher/trojan-source/blob/main/Python/commenting-out.py)\":\n\n```console\n$ xclip -out -selection CLIPBOARD -display :1000 | unicode-show\n\u003cstdin\u003e:4: if access_level != 'none[U+202E][U+2066]': # Check if admin [U+2069][U+2066]' and access_level != 'user\n   -\u003e '\\u202e' (U+202E, RIGHT-TO-LEFT OVERRIDE, Cf)\n   -\u003e '\\u2066' (U+2066, LEFT-TO-RIGHT ISOLATE, Cf)\n   -\u003e '\\u2069' (U+2069, POP DIRECTIONAL ISOLATE, Cf)\n   -\u003e '\\u2066' (U+2066, LEFT-TO-RIGHT ISOLATE, Cf)\n\u003cstdin\u003e:5: [missing newline at end]\n```\n\nHowever, this approach has one drawback: you are parsing the clipboard, which is dynamic by nature. Even if it doesn’t contain Unicode characters or the data seems harmless, you shouldn’t assume it will remain that way a second later. You should save the clipboard’s contents to a static file and then analyze it.\n\n```console\n$ xclip -out -selection CLIPBOARD -display :1000 | tee static.txt | unicode-show\n...\n```\n\n#### Transferring a file\n\nTo transfer a file, or, more commonly, an image, the application should allow you to do so. And you should consider what your goal is in this case: Do you want to copy an image to the file system? Or do you want to copy an image between two X servers? The first option is like downloading the file, while the second is more useful for copying an image between two X11 applications.\n\nTo transfer an image and save it to the file system or use another application to view it, you first need to know the **TARGET**, which usually refers to the file type and then specify the target when downloading the file.\n\n```console\n$ xclip -out -selection CLIPBOARD -target TARGETS -display :1000\nTIMESTAMP\nTARGETS\nSAVE_TARGETS\nMULTIPLE\ntext/x-moz-url\nchromium/x-internal-source-rfh-token\nchromium/x-source-url\nimage/png\ntext/html\n$ xclip -out -selection CLIPBOARD -target image/png -display :1000 | feh -\n```\n\nTo transfer a file between two X servers, you can use [xclip](https://freshports.org/sysutils/xclip) again. Both the output and the input must have the **-target** set.\n\n```console\n$ xclip -out -selection CLIPBOARD -target image/png -display :1000 |\\\n    xclip -in -selection CLIPBOARD -target image/png -display :0\n```\n\nAn easier way is to use [xclipsync](https://freshports.org/sysutils/xclipsync), which automatically detects the **-target** on both sides.\n\n```console\n$ xclipsync -O -a :1000 -b :0\n```\n\n#### Synchronizing the clipboard between two X servers\n\nUnlike the data transfer described above, synchronization occurs in real time, implicitly, and just like when using X11 applications on the same X server. To do this, you should use a tool like [xclipsync](https://freshports.org/sysutils/xclipsync), which is specifically designed for this purpose.\n\n```console\n$ xclipsync -a :1000 -b :1001\n```\n\nRegardless of what is copied to `:1000`, when an X11 application on `:1001` requests the contents of the clipboard, the X11 application on `:1000` will transfer the data encoded in UTF-8.\n\nHowever, this raises a usability issue: although you can easily retrieve the display server for a specific jail using `appjail-jail(1)` `list` (or `get`), this number may change when the jail is recreated. To fix this, you can use the following command:\n\n```console\nxclipsync -a :`appjail jail list -Hj x11appjail-telegram-desktop-15000_default x11_display` -b :`appjail jail list -Hj x11appjail-chromium-15000_default x11_display`\n```\n\nAlthough this solves the problem of accessing the display server we don't know, it's even worse, since you have to type more characters. To improve this, append the following to your `~/.profile`:\n\n```sh\nxclipsync_host()\n{\n    local uid jail x11_display\n    local app=\"$1\" profile=\"${2:-default}\"\n\n    if [ -z \"${app}\" ]; then\n        echo \"usage: xclipsync_host \u003capp\u003e [\u003cprofile\u003e]\" \u003e\u00262\n        return 64 # EX_USAGE\n    fi\n\n    uid=`id -u`\n    jail=\"x11appjail-${app}-${uid}_${profile}\"\n    x11_display=`appjail jail list -Hj \"${jail}\" x11_display` || return $?\n\n    test -n \"${x11_display}\" || echo \"${jail}: No such display\" \u003e\u00262\n    test -n \"${x11_display}\" || return 66 # EX_NOINPUT\n\n    xclipsync -a \"${DISPLAY}\" -b :\"${x11_display}\"\n}\n\nxclipsync_apps()\n{\n    local uid\n    local jail1 jail2\n    local profile1 profile2\n    local _app1 _app2\n    local jail1_x11_display jail2_x11_display\n    local app1=\"$1\" app2=\"$2\"\n\n    if [ -z \"${app1}\" -o -z \"${app2}\" ]; then\n        echo \"usage: xclipsync_apps \u003capp#1\u003e[:\u003cprofile\u003e] \u003capp#2\u003e[:\u003cprofile\u003e]\" \u003e\u00262\n        return 64 # EX_USAGE\n    fi\n\n    profile1=`printf \"%s\" \"${app1}\" | cut -s -d: -f2-`\n    test -n \"${profile1}\" || profile1=\"default\"\n\n    _app1=`printf \"%s\" \"${app1}\" | cut -s -d: -f1`\n    test -n \"${_app1}\" \u0026\u0026 app1=\"${_app1}\"\n\n    profile2=`printf \"%s\" \"${app2}\" | cut -s -d: -f2-`\n    test -n \"${profile2}\" || profile2=\"default\"\n\n    _app2=`printf \"%s\" \"${app2}\" | cut -s -d: -f1`\n    test -n \"${_app2}\" \u0026\u0026 app2=\"${_app2}\"\n\n    uid=`id -u`\n\n    jail1=\"x11appjail-${app1}-${uid}_${profile1}\"\n    jail2=\"x11appjail-${app2}-${uid}_${profile2}\"\n\n    jail1_x11_display=`appjail jail list -Hj \"${jail1}\" x11_display` || return $?\n    test -n \"${jail1_x11_display}\" || echo \"${jail1}: No such display\" \u003e\u00262\n    test -n \"${jail1_x11_display}\" || return 66 # EX_NOINPUT\n\n    jail2_x11_display=`appjail jail list -Hj \"${jail2}\" x11_display` || return $?\n    test -n \"${jail2_x11_display}\" || echo \"${jail2}: No such display\" \u003e\u00262\n    test -n \"${jail2_x11_display}\" || return 66 # EX_NOINPUT\n\n    xclipsync -a :\"${jail1_x11_display}\" -b :\"${jail2_x11_display}\"\n}\n```\n\nReload that file, open a new window in tmux, or reopen the console to load the functions. After that, you can synchronize the clipboard between a jailed X11 application and your host (or wherever `DISPLAY` points) using `xclipsync_host`, and synchronize the clipboard between two jailed X11 applications using `xclipsync_apps`. Note that while `xclipsync(1)` supports more selections (such as `PRIMARY` or `SECONDARY`), we use the default, `CLIPBOARD`, which is the most common.\n\n**Synchronizing the host and a jailed X11 application**:\n\n```console\n$ xclipsync_host chromium\n```\n\n**Synchronizing two jailed X11 applications**:\n\n```console\n$ xclipsync_apps telegram-desktop chromium\n```\n\n### Opening an image in a jail\n\nA web browser is deployed in a jail, you download a .jpg file, Thunar is opened as described above, and you click on the potentially malicious .jpg. Except for the .jpg download, all of this occurred on the host rather than in a jail. We can fix this.\n\n```sh\npkg remove -y feh # if you have feh installed\nmkdir -p ~/AppScripts\nfetch -o ~/AppScripts/feh.appscript https://github.com/DtxdF/x11appjail/releases/latest/download/feh-amd64.appscript\nchmod +x ~/AppScripts/feh.appscript\nX11APPJAIL_INSTALL=1 X11APPJAIL_NO_EPHEMERAL=1 ~/AppScripts/feh.appscript\n```\n\nYou can just install and run the AppScript, but the core idea is to integrate the AppScript with our DE, in this case XFCE, but since we'll be using standard tools, it will integrate with any desktop environment that complies with the desktop specification.\n\n```sh\ngrep -Ee '^image/' /usr/local/share/mime/types | xargs xdg-mime default feh.desktop\n```\n\nWith the previous command, `feh.desktop` (`~/.local/share/applications/feh.desktop`) will be used to open files that match all MIME types of `image/*`. The next step is to test our changes.\n\n```sh\nxdg-open ~/x11appjail/data/x11appjail-chromium-15000_default/Downloads/cat.jpg\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/feh.png\" /\u003e\n\u003c/p\u003e\n\n### Opening a PDF in a jail\n\nYou see an interesting PDF on a random website, or maybe you receive one in your email client, but you’re worried: you don’t know if the PDF is malicious.\n\nThere’s an AppScript to deploy Evince in an jail, but we’re going to configure Thunar to use [Puck](https://github.com/AppJail-makejails/puck) before opening the PDF in that environment, in order to add another layer of protection.\n\nFirst, let's deploy Evince.\n\n```sh\n#pkg remove -y evince # if you have evince installed\n# or\npkg remove -y evince-lite # if you have evince-lite installed\nmkdir -p ~/AppScripts\nfetch -o ~/AppScripts/evince.appscript https://github.com/DtxdF/x11appjail/releases/latest/download/evince-amd64.appscript\nchmod +x ~/AppScripts/evince.appscript\nX11APPJAIL_INSTALL=1 X11APPJAIL_NO_EPHEMERAL=1 X11APPJAIL_WITH_CACHE=1 ~/AppScripts/evince.appscript\n```\n\nThen, map the MIME type `application/pdf` to `org.gnome.Evince.desktop` (`~/.local/share/applications/org.gnome.Evince.desktop`).\n\n```sh\nxdg-mime default org.gnome.Evince.desktop application/pdf\n```\n\nThe next step is to test our changes.\n\n```sh\nxdg-open \"$HOME/x11appjail/data/x11appjail-chromium-15000_default/Downloads/manco17sosp-lightvm.pdf\"\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/evince-1.png\" /\u003e\n\u003c/p\u003e\n\nWith this approach, when a PDF is opened, it is automatically copied to the jail and then opened. The next step is to configure Thunar to open this AppScript, but using Puck to add another layer of protection. The basic idea is to add a [custom action](https://docs.xfce.org/xfce/thunar/custom-actions) in Thunar.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-evince-1.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-evince-2.png\" /\u003e\n\u003c/p\u003e\n\nThe command we should use is\n\n```sh\n/usr/bin/env X11APPJAIL_WITH_CACHE=0 X11APPJAIL_WITH_PUCK=1 /home/user/x11appjail/apps/x11appjail-evince-15000_default/START %f\n```\n\nRemember that you can redefine environment variables at runtime, and the AppScript will honor them. We do this by setting `X11APPJAIL_WITH_CACHE=0` to invalidate any PDFs we’ve created previously, and `X11APPJAIL_WITH_PUCK=1` to convert the potentially untrusted PDF into a trusted one. And that’s where the magic happens...\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-evince-3.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-evince-4.png\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/thunar-evince-5.png\" /\u003e\n\u003c/p\u003e\n\n### Opening URLs\n\nYou have an X11 application, such as an instant messaging app or an email client, and you simply want to click to open a web page. However, you can’t do that because the jail containing the application doesn’t have a web browser to open it, and installing a web browser in that same jail isn’t a good idea. In x11appjail, you can safely open a URL using the [OpenURL](Services/OpenURL/README.md) service.\n\nYou can run this service as follows:\n\n```sh\nenv X11APPJAIL_SERVICE=OpenURL X11APPJAIL_SERVICE_FROM=telegram-desktop \\\n    ~/x11appjail/apps/x11appjail-chromium-15000_default/START\n```\n\nTherefore, if a process running inside jail (in this case, `x11appjail-telegram-desktop-15000_default`) under the DE/WM (in this case, [x11-wm/ratpoison](https://freshports.org/x11-wm/ratpoison)) attempts to open a URL, a dialog box will appear on the host to confirm whether you really want to open the URL.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/OpenURL.png\" /\u003e\n\u003c/p\u003e\n\nHowever, it is not feasible to run this service in the foreground. Unlike the clipboard, which should only be used when absolutely necessary, this service does not open any URLs unless authorized to do so; therefore, it is feasible to let it continue running as long as our session is active. We can use XDG Autostart to run the service when our session starts and stop it when it ends, but a better alternative is to use XDG Autostart to start [sysutils/s6](https://freshports.org/sysutils/s6) as a lightweight supervisor and then define certain tasks. This has the advantage that we can debug more easily and control the execution flow, which is necessary, as we will see.\n\nIn XFCE, we can do this through the GUI: `Settings \u003e Session and Startup \u003e Application Autostart`. The resulting .desktop files are:\n\n**~/.config/autostart/start-s6.desktop**:\n\nThis .desktop file is responsible for running s6 after we start our DE session.\n\n```\n[Desktop Entry]\nEncoding=UTF-8\nVersion=0.9.4\nType=Application\nName=start-s6\nComment=\nExec=s6-svscan /home/user/.s6/sv\nOnlyShowIn=XFCE;\nRunHook=0\nStartupNotify=false\nTerminal=false\nHidden=false\n\n```\n\n**~/.config/autostart/stop-s6.desktop**:\n\nAnd this will stop s6 and the services. This is necessary because, if we restart our DE session, s6 will not inherit the new environment variables such as the one used by D-Bus.\n\n```\n[Desktop Entry]\nEncoding=UTF-8\nVersion=0.9.4\nType=Application\nName=stop-s6\nComment=\nExec=s6-svscanctl -t /home/user/.s6/sv\nOnlyShowIn=XFCE;\nRunHook=1\nStartupNotify=false\nTerminal=false\nHidden=false\n\n```\n\nThe tree directory of *scandir* is, at least on my `$HOME`:\n\n```\n$ tree -pug ~/.s6/\n[drwxr-xr-x user     user    ]  /home/user/.s6/\n└── [drwxr-xr-x user     user    ]  sv\n    └── [drwxr-xr-x user     user    ]  OpenURL-telegram\n        ├── [drwx-ws--T user     user    ]  event\n        ├── [-rwxr-xr-x user     user    ]  finish\n        ├── [-rwxr-xr-x user     user    ]  run\n        └── [drwx------ user     user    ]  supervise\n            ├── [prw------- user     user    ]  control\n            ├── [-rw-r--r-- user     user    ]  death_tally\n            ├── [-rw-r--r-- user     user    ]  lock\n            └── [-rw-r--r-- user     user    ]  status\n\n5 directories, 6 files\n```\n\nHowever, only the following files are relevant:\n\n**~/.s6/sv/OpenURL-telegram/run**:\n\n```sh\n#!/bin/sh\n\nexec env X11APPJAIL_SERVICE=OpenURL X11APPJAIL_SERVICE_FROM=telegram-desktop \\\n    \"${HOME}/x11appjail/apps/x11appjail-chromium-15000_default/START\"\n```\n\n**~/.s6/sv/OpenURL-telegram/finish**:\n\n```sh\n#!/bin/sh\n\nkill -- -\"$4\"\n```\n\nThe rest of the files just indicate that s6 is currently running. All you have to do is restart your session, and s6 will start running along with the service.\n\n```console\n$ s6-svstat ~/.s6/sv/OpenURL-telegram\nup (pid 75838 pgid 75838) 2537 seconds\n```\n\nYou can reinstall the AppScript and it will work fine, but the service will continue to use the old code, so it's best to stop the service, reinstall the AppScript, and then start again the service.\n\n```console\n$ s6-svc -d ~/.s6/sv/OpenURL-telegram\n$ s6-svstat ~/.s6/sv/OpenURL-telegram\ndown (signal SIGTERM) 13 seconds, normally up, ready 13 seconds\n$ pet exec -t chrome\n...\n$ s6-svc -u ~/.s6/sv/OpenURL-telegram\n```\n\n**Note**: If you plan to use a supervisor other than s6, make sure it allows you to terminate the process group and that it uses `SIGTERM` instead of `SIGKILL`, since AppJail or other scripts may use this signal.\n\n### Receiving notifications\n\nModern X11 applications, especially those that act as clients for one or more protocols, such as a web browser, an email client, or an instant messaging app, typically support notifications. Although you can install a notification daemon in each jail and receive notifications this way, the problem is that it doesn’t adapt very well to every new application deployed on your system, and they will likely become useless because, if the window isn’t active, you won’t realize you’ve received a notification and will miss it after a while.\n\nIn x11appjail, there is a service that solves this problem called [Notification](Services/Notification/README.md).\n\n```sh\nexec env X11APPJAIL_SERVICE=Notification X11APPJAIL_SERVICE_FROM=thunderbird \\\n    ~/x11appjail/apps/x11appjail-thunderbird-15000_default/START\n```\n\nAs with OpenURL, it could be better to use s6 to run this service.\n\n**~/.s6/sv/Notification-thunderbird/run**:\n\n```sh\n#!/bin/sh\n\nexec env X11APPJAIL_SERVICE=Notification X11APPJAIL_SERVICE_FROM=thunderbird \\\n    \"${HOME}/x11appjail/apps/x11appjail-thunderbird-15000_default/START\"\n```\n\n**~/.s6/sv/Notification-thunderbird/finish**:\n\n```sh\n#!/bin/sh\n\nkill -- -\"$4\"\n```\n\nAssuming that s6 is already running:\n\n```sh\n$ s6-svscanctl -a ~/.s6/sv\n$ s6-svstat ~/.s6/sv/Notification-thunderbird/\nup (pid 35432 pgid 35432) 5138 seconds\n```\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/img/Notification.png\" /\u003e\n\u003c/p\u003e\n\n**Note**: [sysutils/dunst](https://freshports.org/sysutils/dunst) is installed in x11appjail applications that use notifications, but it is only configured when this service is running; therefore, if you already have the application running (such as Thunderbird in the previous example), you may need to rerun the AppScript, since the application inside the jail might run `dunst(1)` without the configuration that calls the installed agent. If you see notifications within the X server created by `Xephyr(1)` instead of by your host, this means you need to restart the AppScript: simply close the application and rerun the AppScript from your menu launcher.\n\n## Demo\n\nIn these videos, [Overlord](https://github.com/DtxdF/overlord) is used to create a pristine VM to demostrate how Brave will be executed using its AppScript.\n\nhttps://github.com/user-attachments/assets/c60b3f22-5e91-45a6-9be7-ea7397d202e9\n\nhttps://github.com/user-attachments/assets/f91cbf4a-54a1-4df8-95fe-c02942e96879\n\nhttps://github.com/user-attachments/assets/da38f509-c848-40af-97e6-515801251618\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtxdf%2Fx11appjail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdtxdf%2Fx11appjail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdtxdf%2Fx11appjail/lists"}