appjail 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {appjail-0.2.0 → appjail-0.2.2}/PKG-INFO +112 -81
  2. {appjail-0.2.0 → appjail-0.2.2}/README.md +111 -80
  3. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/amsource.py +13 -5
  4. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/catalog.py +72 -7
  5. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_index.py +20 -7
  6. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_updateimage.py +1 -2
  7. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/desktop.py +2 -6
  8. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/index.py +2 -2
  9. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/indexstore.py +28 -24
  10. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/jail.py +2 -3
  11. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/manifest.py +2 -5
  12. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/portal.py +5 -10
  13. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/runner.py +59 -41
  14. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/runtimejail.py +29 -17
  15. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/store.py +16 -0
  16. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/updater.py +1 -5
  17. {appjail-0.2.0 → appjail-0.2.2}/.gitignore +0 -0
  18. {appjail-0.2.0 → appjail-0.2.2}/LICENSES/MPL-2.0.txt +0 -0
  19. {appjail-0.2.0 → appjail-0.2.2}/REUSE.toml +0 -0
  20. {appjail-0.2.0 → appjail-0.2.2}/pyproject.toml +0 -0
  21. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/__init__.py +0 -0
  22. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/__main__.py +0 -0
  23. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/appimage.py +0 -0
  24. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/attest.py +0 -0
  25. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cli.py +0 -0
  26. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_add.py +0 -0
  27. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_addimage.py +0 -0
  28. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_apps.py +0 -0
  29. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_desktop.py +0 -0
  30. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_open.py +0 -0
  31. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_portals.py +0 -0
  32. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/cmd_update.py +0 -0
  33. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/console.py +0 -0
  34. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/dbusproxy.py +0 -0
  35. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/fetcher.py +0 -0
  36. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/github.py +0 -0
  37. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/grants.py +0 -0
  38. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/history.py +0 -0
  39. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/imagesrc.py +0 -0
  40. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/permargs.py +0 -0
  41. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/seccomp.py +0 -0
  42. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/services.py +0 -0
  43. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/snappy.py +0 -0
  44. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/textsafe.py +0 -0
  45. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/tools.py +0 -0
  46. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/valid.py +0 -0
  47. {appjail-0.2.0 → appjail-0.2.2}/src/appjail/wayland.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: appjail
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Sandboxed AppImage manager built on bubblewrap
5
5
  License-Expression: MPL-2.0
6
6
  License-File: LICENSES/MPL-2.0.txt
@@ -29,7 +29,7 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  A small command-line manager for [AppImages](https://appimage.org/) that runs every app inside a
31
31
  [bubblewrap](https://github.com/containers/bubblewrap) sandbox. It works in user mode (no sudo), supports GUI apps
32
- under Wayland, and treats the software you install as untrusted.
32
+ on Wayland (and X11-only apps through a private Xwayland), and treats the software you install as untrusted.
33
33
 
34
34
  *appjail is young software: read the [limits](#limits) before relying on it.*
35
35
 
@@ -40,8 +40,8 @@ is built around the opposite assumptions:
40
40
 
41
41
  - **Declarative manifests, no remote code.** An app is a small TOML file: source, pinned hash, permissions. Nothing
42
42
  fetched from the network is ever executed or sourced, and appjail has no self-update.
43
- - **You name the source.** You give a GitHub `owner/repo`, or an AppImage file or URL yourself. That is the only
44
- place the app is fetched from, whatever an AppImage's embedded update information says.
43
+ - **You name the source.** You give a GitHub `owner/repo`, or an AppImage file or URL. That is the only place the
44
+ app is fetched from, whatever an AppImage's embedded update information says.
45
45
  - **Downloads are jailed.** The download, the extraction and the verification tools run in throwaway sandboxes
46
46
  with only the access they need (a private network namespace for downloads, none for extraction).
47
47
  - **Everything is denied by default.** An app starts with a private home and a restricted Wayland socket. Network,
@@ -53,13 +53,16 @@ is built around the opposite assumptions:
53
53
 
54
54
  - Linux on x86-64 with unprivileged user namespaces. Tested on Debian 13 and Ubuntu 26.04; older systems are
55
55
  not supported.
56
- - Python 3.12 or newer (tested with 3.13 on Debian 13 and 3.14 on Ubuntu 26.04; 3.12 is the declared minimum and is not
57
- run by the tests). appjail uses only the standard library.
58
- - `bubblewrap` and `squashfs-tools` (`unsquashfs`) are required.
59
- - Recommended: `passt` (the `pasta` command) for apps with network access, `xdg-dbus-proxy` for D-Bus filtering,
60
- `xwayland` for X11-only apps, `git` for the manifest history and the search index.
61
- - A Wayland compositor for GUI apps. Apps that need X11 run on a private Xwayland inside their own sandbox; the host
62
- X11 socket is never shared.
56
+ - Python 3.12 or newer (tested with 3.13 on Debian 13 and 3.14 on Ubuntu 26.04; 3.12 is the declared minimum and is
57
+ not run by the tests). appjail uses only the standard library.
58
+ - Required: `bubblewrap` and `squashfs-tools` (`unsquashfs`).
59
+ - Needed for specific features:
60
+ - `passt` (the `pasta` command): apps with the `network` permission, which are refused without it. Also gives
61
+ the download jails a private network namespace; without it they use the host network and print a warning.
62
+ - `xdg-dbus-proxy`: D-Bus filtering and portals.
63
+ - `xwayland`: X11-only apps.
64
+ - `git`: the search index (`index update`) and the manifest history.
65
+ - A Wayland compositor for GUI apps. The host X11 socket is never shared.
63
66
 
64
67
  On Debian or Ubuntu:
65
68
 
@@ -80,14 +83,15 @@ appjail --help
80
83
  Or from a clone (pip downloads only the build backend, Hatchling):
81
84
 
82
85
  ```sh
83
- git clone <repository-url> appjail
86
+ git clone https://github.com/alberto743/appjail.git
84
87
  cd appjail
85
88
  pipx install . # or: pip install --user .
86
- appjail --help
87
89
  ```
88
90
 
89
91
  To try it without installing, run it from the clone: `PYTHONPATH=src python3 -m appjail --help`.
90
92
  appjail never updates itself: upgrade with `pipx upgrade appjail` (or `git pull` and reinstall) after reading what changed.
93
+ `appjail version` (or `--version`) prints the installed version, which comes from the git tag at build time (`0.2.0` on a
94
+ release, `0.2.1.dev4+g1a2b3c4` four commits after it) and is `unknown` when run from a source tree that is not installed.
91
95
 
92
96
  Data lives in `~/.local/share/appjail` (images, extracted trees, private homes, staging) and `~/.config/appjail`
93
97
  (manifests, tracked in a small local git repository). `APPJAIL_DATA_DIR` and `APPJAIL_CONFIG_DIR` override them.
@@ -102,16 +106,26 @@ appjail update # check every app for a newer release
102
106
  appjail remove lagrange # delete the app and its private home
103
107
  ```
104
108
 
105
- `add` also takes `--tag TAG` (a specific release instead of the latest), `--name`, `--prerelease`, and the permission
106
- options below at install time (`--allow`, `--dir`, `--dbus`, `--dbus-own`, `--portal`, `--display xwayland`,
107
- `--no-audio`, `--no-gpu`). `run` takes `--dir PATH[:ro|:rw]` and `--file PATH[:rw]` for one run only, and passes
108
- everything after `--` to the app (`appjail run lagrange -- --help`). `remove` and `perms` ask for confirmation unless
109
- given `--yes`.
109
+ `remove` and `perms` ask for confirmation unless given `--yes`. `appjail COMMAND --help` lists every option.
110
+ `GITHUB_TOKEN`, if set, raises the GitHub API rate limit; it is only ever sent to `api.github.com`.
111
+
112
+ ## Installing apps
113
+
114
+ **From a GitHub release.** `appjail add owner/repo` shows the repository owner, account age, stars and fork or archive
115
+ status before it downloads anything, and asks if several release assets match. By default an app gets audio and GPU
116
+ access and nothing else.
117
+
118
+ | Option | Effect |
119
+ |---|---|
120
+ | `--tag TAG` | A specific release instead of the latest; the app is then *pinned* and `update` leaves it alone. |
121
+ | `--prerelease` | Follow pre-releases. |
122
+ | `--name NAME` | The app's name (default: from the project). |
123
+ | `--allow`, `--dir`, `--dbus`, `--dbus-own`, `--portal`, `--display`, `--no-audio`, `--no-gpu` | Permissions at install time (see [Permissions](#permissions)). |
124
+ | `--desktop [--mime TYPE]` | Also create a menu entry (see [Menu entry](#menu-entry-and-opening-files)). |
110
125
 
111
- `add` shows the repository owner, account age, stars and fork or archive status before it downloads anything, and
112
- asks if several release assets match. By default an app gets audio and GPU access and nothing else.
126
+ `run` passes everything after `--` to the app (`appjail run lagrange -- --help`).
113
127
 
114
- **Apps that are not GitHub releases.** Give the AppImage itself:
128
+ **From a file or URL.** For apps that are not GitHub releases, give the AppImage itself:
115
129
 
116
130
  ```sh
117
131
  appjail add --file ~/Downloads/Tool-x86_64.AppImage [--name tool] [--sha256 HEX]
@@ -119,49 +133,60 @@ appjail add --url https://example.org/dl/Tool-x86_64.AppImage [--name tool] [--s
119
133
  appjail update tool --file ~/Downloads/Tool-new.AppImage # an app added from a file
120
134
  ```
121
135
 
122
- Nothing identifies who published such a file, so these apps are tier 5 (trust on first use) or tier 4 when you pass the
123
- hash the publisher gives with `--sha256` (a mismatch aborts). A URL must be https and is downloaded in the jail (1 GiB
124
- at most unless `--max-size`); redirects may go to any host, and a redirect through plain http asks for an extra
136
+ Nothing identifies who published such a file, so these apps are tier 5 (trust on first use), or tier 4 when you pass
137
+ the hash the publisher gives with `--sha256` (a mismatch aborts). A URL must be https and is downloaded in the jail
138
+ (1 GiB at most unless `--max-size`); redirects may go to any host, and a redirect through plain http asks for an extra
125
139
  confirmation unless `--sha256` matches. `update` of a URL app asks the server first with a conditional request and a
126
140
  few small range requests, and downloads the whole file only when something changed.
127
141
 
128
- ### Permissions
142
+ ## Permissions
143
+
144
+ Everything is denied unless granted. Show or change an app's permissions with `perms`:
129
145
 
130
146
  ```sh
131
- appjail perms lagrange # show
132
- appjail perms lagrange --allow network # outbound network only (see below)
133
- appjail perms lagrange --add-dir ~/Documents:rw # a directory, read-only unless :rw
134
- appjail perms lagrange --add-dbus org.example.Name # talk to one session-bus name through a filtering proxy
135
- appjail perms lagrange --add-dbus-own org.example.Name # own a session-bus name (MPRIS player, single instance)
136
- appjail perms lagrange --allow portals # desktop portals (file chooser, notifications, ...)
147
+ appjail perms lagrange # show
148
+ appjail perms lagrange --allow network # outbound network only (see the table)
149
+ appjail perms lagrange --add-dir ~/Documents:rw # a directory, read-only unless :rw
150
+ appjail perms lagrange --add-dbus org.example.Name # talk to one session-bus name through a filtering proxy
151
+ appjail perms lagrange --add-dbus-own org.example.Name # own a session-bus name (MPRIS player, single instance)
152
+ appjail perms lagrange --allow portals # desktop portals (file chooser, notifications, ...)
137
153
  appjail perms lagrange --deny network --rm-dir ~/Documents # take grants back (also --rm-dbus, --rm-dbus-own, --rm-portal)
138
- appjail portals lagrange # files it was granted through the file chooser
139
- appjail portals lagrange --revoke-all # take those grants back (also --revoke DOC_ID)
140
- appjail run lagrange --file ~/report.pdf # one file, for this run only
154
+ appjail portals lagrange # files it was granted through the file chooser
155
+ appjail portals lagrange --revoke-all # take those grants back (also --revoke DOC_ID)
141
156
  ```
142
157
 
143
- **Giving one file.** `run --file PATH` (add `:rw` to allow writing) makes just that file visible, not the rest of its
144
- directory, and appends its path inside the sandbox to the app's arguments. It applies to that run only and is never
145
- saved in the manifest; for a standing grant use a directory, or `portals` and the file chooser. Credentials, browser
146
- profiles and the like are refused as for directories, and shell startup files are never writable. Apps that save by
147
- writing a temporary file and renaming it fail on `:rw`.
148
-
149
158
  | Permission | Effect |
150
159
  |---|---|
151
160
  | `network` | Outbound internet through a private network namespace made by `pasta`. The app cannot reach services on your machine's localhost or its own addresses. Needs `passt`; without it the app is refused rather than put on the host network. |
152
- | `network_host` | The host's own network, including localhost services. For apps that really need them. |
153
- | `audio`, `gpu` | On by default; `--no-audio` and `--no-gpu` at `add` time, or `--deny` later. |
161
+ | `network_host` | The host's own network, including localhost services (`--allow network_host`, which implies `network`). For apps that really need them. |
162
+ | `audio`, `gpu` | On by default; `--no-audio` and `--no-gpu` at `add` time, or `--deny` later. Audio includes the microphone. |
154
163
  | `portals` | The safe set of desktop portals; privileged ones are listed per app with `--add-portal`. |
155
164
  | `userns` | Nested user namespaces (Chromium and Electron sandboxes). Weakens isolation, off by default. |
156
- | `dirs` | Directories mapped into the private home. Credentials, browser profiles, shell startup files and system trees are refused. |
157
- | `dbus`, `dbus_own` | Session-bus names the app may talk to or own, through `xdg-dbus-proxy`. Desktop namespaces are refused. |
165
+ | `dirs` | Directories mapped into the private home (`--dir` / `--add-dir`). Credentials, browser profiles, shell startup files and system trees are refused. |
166
+ | `dbus_filter`, `dbus_own` | Session-bus names the app may talk to (`--dbus`) or own (`--dbus-own`), through `xdg-dbus-proxy`. Desktop namespaces are refused. |
158
167
  | `display` | `wayland` (default) or `xwayland`. |
159
168
 
169
+ **Temporary grants.** `run APP --dir PATH[:ro|:rw]` and `run APP --file PATH[:rw]` grant access for that run only and are never
170
+ saved. `--file` makes just that one file visible, not the rest of its directory, and appends its path inside the sandbox to
171
+ the app's arguments. Credentials, browser profiles and the like are refused as for directories, and shell startup files are
172
+ never writable. Apps that save by writing a temporary file and renaming it fail on `:rw`. For a standing grant use a
173
+ directory, or the portal file chooser.
174
+
160
175
  Every sandbox also drops all capabilities, has its own PID, IPC and UTS namespaces, a read-only system, a minimal
161
176
  `/dev`, a generated `/etc/passwd`, and a seccomp filter. The extracted app tree is hashed and checked at every
162
177
  launch.
163
178
 
164
- ## How trust works
179
+ ## Menu entry and opening files
180
+
181
+ `appjail desktop NAME [--mime TYPE]... [--mime-all] [--rw]` (also `add --desktop`) creates a menu entry that starts the
182
+ app through appjail. appjail generates it itself; the AppImage's own `.desktop` is never used. File types are opt-in:
183
+ only the ones you pick show under "Open with", and appjail never makes the app a default. Opening a file passes just
184
+ that file to the app, read-only unless `--rw` (`appjail open NAME -- FILE...`). `desktop --remove` and `--prune` delete
185
+ entries; `remove` does it too.
186
+
187
+ ## Trust and updates
188
+
189
+ ### Evidence tiers
165
190
 
166
191
  Each app is pinned by hash and carries an *evidence tier* that `update` will never silently lower:
167
192
 
@@ -176,22 +201,33 @@ Tier 2 (GPG signatures) is not implemented. Most AppImages will land on tier 3 t
176
201
  GitHub, "from the same account"), not "safe". `update APP --sha256 HEX` earns tier 4 for a tier 5 app when the hash
177
202
  matches, and a tier 4 URL or file app refuses new content that comes without one.
178
203
 
179
- `appjail update` re-resolves the pinned repository, downloads the candidate in the jail, verifies it, and shows you the
180
- version, the hashes, the tier and the release link. A release must be seen for the quarantine period before it can
204
+ ### Updating
205
+
206
+ `appjail update [APP]` re-resolves the pinned repository, downloads the candidate in the jail, verifies it, and shows you
207
+ the version, the hashes, the tier and the release link. A release must be seen for the quarantine period before it can
181
208
  be installed (`--quarantine DAYS`). For tier 4 apps appjail records the candidate's hash when it is first seen and refuses
182
209
  the install if the content changed in the meantime. A change of repository owner or a drop in tier blocks the update.
183
210
  `--channel stable|prerelease|pinned` chooses which releases an app follows.
184
211
 
185
- What `update` prints per app: *up to date*; *waiting* (a candidate was seen and its quarantine has not ended: run `update`
186
- again later, it needs no action); *blocked* (the owner changed, the evidence got weaker, or the content changed during
187
- the quarantine: nothing was installed and the message says why); *declined* (you answered no); *pinned* (the app follows
188
- no releases). The exit status is 1 if any app was blocked or failed. A URL app's candidate is kept on disk during
189
- the quarantine and installed from there (`--verify` downloads it again first); a file app has no quarantine, you hold the bytes.
212
+ What `update` prints per app:
213
+
214
+ - *up to date*;
215
+ - *waiting*: a candidate was seen and its quarantine has not ended; run `update` again later, no action needed;
216
+ - *blocked*: the owner changed, the evidence got weaker, or the content changed during the quarantine; nothing was
217
+ installed and the message says why;
218
+ - *declined*: you answered no;
219
+ - *pinned*: the app follows no releases.
220
+
221
+ The exit status is 1 if any app was blocked or failed. A URL app's candidate is kept on disk during the quarantine and
222
+ installed from there (`--verify` downloads it again first); a file app has no quarantine, you hold the bytes.
223
+
224
+ Every change to the manifests is committed to a local git repository (`appjail history [APP] [--limit N]` lists them), and
225
+ `appjail run` warns if a manifest was edited outside appjail.
190
226
 
191
227
  ## Searching
192
228
 
193
- `search` looks through **catalogs**: lists of apps with, where known, the GitHub repository each one comes from. A hit
194
- is only a suggestion; installing is always `appjail add owner/repo` (or `--file`/`--url`), after you have checked
229
+ `search` looks through **catalogs**: lists of apps with, where known, the project each one comes from. A hit
230
+ is only a suggestion; installing is always `appjail add owner/repo` (or `--url`), after you have checked
195
231
  the owner. Catalogs are untrusted data: appjail parses them strictly, strips terminal escape sequences from every
196
232
  text, and never runs anything from them.
197
233
 
@@ -213,7 +249,8 @@ its repository in a network-only jail (only the list and the install scripts, no
213
249
  each script as text to find the `SITE=` line, and keeps the result as a catalog. The scripts themselves are not
214
250
  kept. Sources you add yourself provide a catalog directly. Every update shows the number of apps, the apps added and
215
251
  removed, and every app whose repository changed, with a loud warning when the owner changed, and asks before it
216
- replaces the catalog. One previous catalog is kept per source for `index rollback`.
252
+ replaces the catalog. One previous catalog is kept per source for `index rollback`; sources are stored in
253
+ `~/.local/share/appjail/index/`.
217
254
 
218
255
  **Catalog format.** One UTF-8 JSON document, at most 16 MiB and 50,000 apps:
219
256
 
@@ -224,7 +261,11 @@ replaces the catalog. One previous catalog is kept per source for `index rollbac
224
261
  "origin": "https://example.org/my-apps",
225
262
  "revision": "2026-10-22",
226
263
  "apps": [
227
- {"name": "lagrange", "description": "A Gemini browser", "source": "skyjake/lagrange"},
264
+ {"name": "lagrange", "description": "A Gemini browser", "source": "github:skyjake/lagrange"},
265
+ {"name": "tool-a", "source": "codeberg:user/tool-a"},
266
+ {"name": "tool-b", "source": "gitlab:gitlab.example.org/group/sub/tool-b"},
267
+ {"name": "tool-c", "source": "forgejo:git.example.net/user/tool-c"},
268
+ {"name": "tool-d", "source": "url:https://example.com/dl/tool-d.AppImage"},
228
269
  {"name": "tool", "description": "Hosted elsewhere", "note": "resolve manually: https://example.org/tool"}
229
270
  ]
230
271
  }
@@ -239,12 +280,14 @@ replaces the catalog. One previous catalog is kept per source for `index rollbac
239
280
  | `apps` | yes | A list of objects, with unique `name`s. |
240
281
  | `apps[].name` | yes | The name to search for: letters, digits, `.`, `_`, `+`, `-`; starts with a letter or digit; up to 81 characters. |
241
282
  | `apps[].description` | no | One line of text, up to 300 characters. |
242
- | `apps[].source` | no | A GitHub `owner/repo`. Anything else is refused: put other locations in `note`. |
283
+ | `apps[].source` | no | Where the app comes from, as `kind:target`: `github:owner/repo`, `gitlab:HOST/group/.../project`, `forgejo:HOST/owner/repo`, `codeberg:owner/repo` (shorthand for `forgejo:codeberg.org/...`), `url:https://...`. The host is always explicit (`gitlab:gitlab.com/...`), without port or credentials. Anything else, including `file:` and a bare `owner/repo`, is refused: put other locations in `note`. |
243
284
  | `apps[].note` | no | Why there is no usable `source`, or a flag such as a repackager. Up to 200 characters. |
244
285
 
245
286
  Unknown keys are errors, so a catalog means the same thing to every version that accepts it. A catalog that breaks
246
287
  a rule is refused as a whole and nothing changes. appjail writes the catalog it validated, not the downloaded bytes.
247
- Sources are listed in `~/.local/share/appjail/index/`.
288
+ `search` offers `appjail add` for `github:` and `url:` sources only; GitLab and Forgejo sources are shown as information
289
+ until appjail can install from those forges. An update that moves an app to another kind, host or owner is flagged as an
290
+ owner change.
248
291
 
249
292
  ## Command reference
250
293
 
@@ -254,7 +297,7 @@ Sources are listed in `~/.local/share/appjail/index/`.
254
297
  | `add --file PATH` / `add --url URL` | Install an AppImage you give yourself; `--sha256 HEX` confirms it (tier 4). |
255
298
  | `run APP [-- ARGS]` | Run an app; `--dir PATH[:ro\|:rw]` and `--file PATH[:rw]` grant access for this run only. |
256
299
  | `list` | Installed apps, versions, sources, tiers, permissions and updates seen. |
257
- | `update [APP]` | Check, quarantine and install new versions (see "How trust works"). |
300
+ | `update [APP]` | Check, quarantine and install new versions (see [Updating](#updating)). |
258
301
  | `perms APP` | Show or change an app's permissions. |
259
302
  | `portals APP` | List or revoke the files an app was granted through the file chooser. |
260
303
  | `desktop APP` / `open APP -- FILE...` | Menu entry, and opening files with an app. |
@@ -262,25 +305,9 @@ Sources are listed in `~/.local/share/appjail/index/`.
262
305
  | `history [APP]` | Recorded changes to the manifests. |
263
306
  | `search WORDS` | Suggestions from the catalogs. |
264
307
  | `index add\|remove\|update\|rollback\|status` | Manage the catalogs `search` reads (the built-in AM list and any you add). |
265
- | `tools status\|install NAME` | Pinned helper binaries (`gh`, `dwarfs`). |
308
+ | `tools status\|install NAME` | Helper binaries whose URL, size and hash are pinned in appjail's source (`gh` for attestations, `dwarfs` for DwarFS-based AppImages). |
266
309
  | `version` | The installed version. |
267
310
 
268
- `appjail COMMAND --help` lists every option.
269
-
270
- ## Other commands
271
-
272
- - `appjail history [app] [--limit N]`: every change to the manifests, from the local git repository. `appjail run` warns if a
273
- manifest was edited outside appjail.
274
- - `appjail tools status|install NAME`: helper binaries whose URL, size and hash are pinned in appjail's source (`gh` for
275
- attestations, `dwarfs` for DwarFS-based AppImages).
276
- - `appjail desktop NAME [--mime TYPE]... [--mime-all] [--rw]` (also `add --desktop`): a menu entry that starts the app through
277
- appjail, generated by appjail (the AppImage's own `.desktop` is never used). File types are opt-in: only the ones you pick
278
- show under "Open with", and appjail never makes the app a default. Opening a file passes just that file to the app, read-only
279
- unless `--rw` (`appjail open NAME -- FILE...`). `--remove` and `--prune` delete entries; `remove` does it too.
280
- - `appjail version` (or `--version`): the installed version. It comes from the git tag at build time (`0.2.0` on a release, `0.2.1.dev4+g1a2b3c4` four
281
- commits after it); it prints `unknown` when run from a source tree that is not installed.
282
- - Set `GITHUB_TOKEN` to raise the GitHub API rate limit. It is only ever sent to `api.github.com`.
283
-
284
311
  ## Troubleshooting
285
312
 
286
313
  | Message or symptom | Cause and fix |
@@ -288,7 +315,7 @@ Sources are listed in `~/.local/share/appjail/index/`.
288
315
  | bwrap fails with `Operation not permitted` or `setting up uid map: Permission denied` | Unprivileged user namespaces are blocked. On Ubuntu, AppArmor can restrict them (`kernel.apparmor_restrict_unprivileged_userns`); the project's CI relaxes it with `sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`. That changes a system-wide protection, so decide for yourself whether to do it, or give bubblewrap its own AppArmor profile. |
289
316
  | `no usable Wayland session` or `Wayland socket not found` | `run` needs a Wayland compositor and `WAYLAND_DISPLAY`/`XDG_RUNTIME_DIR` in the environment (not available over a plain ssh login). Apps that only speak X11 use `perms APP --display xwayland` (needs `xwayland`). |
290
317
  | An app with `network` is refused, naming `passt` | Install `passt`; the app is never put on the host network silently. |
291
- | `GitHub API rate limit reached` | Unauthenticated requests are limited to 60 per hour per address. Set `GITHUB_TOKEN`; it is only sent to `api.github.com`. |
318
+ | `GitHub API rate limit reached` | Unauthenticated requests are limited to 60 per hour per address. Set `GITHUB_TOKEN`. |
292
319
  | `unsquashfs is not installed` | Install `squashfs-tools`, or accept the offer to run the AppImage's own extractor in a jail with no network. |
293
320
  | DwarFS-based AppImage | `appjail tools install dwarfs` fetches the pinned helper. |
294
321
  | An app cannot see a file or folder | Nothing outside its private home is visible by default: `appjail perms APP --add-dir PATH`, or `appjail run APP --file PATH` for one run. |
@@ -297,20 +324,24 @@ Sources are listed in `~/.local/share/appjail/index/`.
297
324
 
298
325
  - **Not audited, not a security boundary you should bet on yet.** bwrap and the Linux kernel do the isolation; appjail
299
326
  configures them conservatively but has had no external review.
327
+ - **The Wayland socket is restricted only if the compositor allows it.** Compositors with `wp_security_context_v1` (sway,
328
+ labwc, Mutter 46+, KWin 6.x) hide their privileged protocols from the app. Others, such as weston, get a warning from
329
+ `run` and the app receives the full host socket.
300
330
  - Releases are resolved on GitHub only. Apps hosted elsewhere appear in `search` and can be added with `--url` or `--file`,
301
- but then nothing identifies the publisher (tier 5). The cheap `update` check for URL apps detects a new version; a
302
- change confined to bytes it does not sample, with the same size and validators, is only seen by a full download.
331
+ but then nothing identifies the publisher (tier 5, or tier 4 with a matching `--sha256`). The cheap `update` check for
332
+ URL apps detects a new version; a change confined to bytes it does not sample, with the same size and validators, is
333
+ only seen by a full download.
303
334
  - A sandboxed app can still do whatever its grants allow. An app with `network` can reach other machines on your LAN.
304
335
  - Granting a directory, a portal or a D-Bus name widens what a compromised app can do.
336
+ - The audio permission includes the microphone.
305
337
  - x86-64 only. The GNOME and KDE portal backends have not been tested (the GTK backend and a real X11-only AppImage
306
338
  have, under headless weston; the portal dialogs themselves are not driven). Real internet IPv6 under `pasta` is
307
339
  untested; IPv6 forwarding is tested in a fake network.
308
340
  - Stopping `appjail run` with SIGINT, SIGTERM or SIGHUP stops the whole jail, including `pasta`. If appjail is killed
309
341
  with SIGKILL, an app with `network` is stopped about a second later by a watcher inside its network namespace (a
310
342
  small empty directory `appjail-life-*` stays in the runtime directory).
311
- - The audio permission includes the microphone.
312
343
 
313
- The full design, the reasoning behind each choice and the verification notes are in [DESIGN.md](DESIGN.md).
344
+ The full design is in [DESIGN.md](DESIGN.md); the reasoning behind each choice, rejected alternatives and deferred features are in [DECISIONS.md](DECISIONS.md).
314
345
 
315
346
  ## Development
316
347