Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4ddc2cf
fix(linux): collapse a read-deny glob's expansion to its covering dir…
ronleizrowice-ant Aug 29, 2026
e025ea8
fix(linux): mount a symlinked read-deny directory at its target and m…
ronleizrowice-ant Aug 29, 2026
0868b7f
ci: run the Linux suite against bubblewrap 0.12.0 as well
ronleizrowice-ant Aug 29, 2026
058e7c5
fix(linux): mount read denies where they land inside the sandbox, not…
ronleizrowice-ant Aug 29, 2026
1f1ab7a
refactor(linux): one record of emitted read-deny tmpfs mounts, bind-d…
ronleizrowice-ant Sep 2, 2026
eb93a10
Merge main into fix/linux-read-deny-glob-collapse
ronleizrowice-ant Sep 10, 2026
f59ad79
refactor(linux): give the read-deny mount trail and the path spelling…
ronleizrowice-ant Sep 10, 2026
dcf5d80
fix(linux): mount a read-deny path after every path that contains it;…
ronleizrowice-ant Sep 10, 2026
709f544
docs: scope the Linux glob notes to what the expansion does
ronleizrowice-ant Sep 10, 2026
92ad199
perf(linux): list each directory of a glob once, and only where the p…
ronleizrowice-ant Sep 10, 2026
0301b30
fix(linux): mount a read-deny, and a path bound back over one, only w…
ronleizrowice-ant Sep 10, 2026
1e40743
fix(linux): keep no deny stub on account of a read-deny tmpfs beneath…
ronleizrowice-ant Sep 10, 2026
2e7295e
docs: say where Linux read-deny mounts and the paths bound back over …
ronleizrowice-ant Sep 10, 2026
f333017
test(linux): list the root's symlinks inside the test, and keep the s…
ronleizrowice-ant Sep 10, 2026
cf0d5cd
test(linux): let the covering-directory tests fail, and gate their ru…
ronleizrowice-ant Sep 12, 2026
8419c30
fix(linux): keep a deny stub only where a read-deny tmpfs can re-open…
ronleizrowice-ant Sep 12, 2026
c618b17
fix(linux): read a deny path named with glob characters and spelled w…
ronleizrowice-ant Sep 12, 2026
f88b4b8
Merge main into fix/linux-read-deny-glob-collapse
ronleizrowice-ant Sep 16, 2026
856895b
test(linux): pin what a read-deny glob reaches behind a directory link
ronleizrowice-ant Sep 16, 2026
36f53f4
test(linux): cover the collapse against the passes the merge brought in
ronleizrowice-ant Sep 16, 2026
85221dc
Linux: keep a read deny the glob walk cannot resolve, and bound the walk
ronleizrowice-ant Sep 16, 2026
43811b8
Linux: keep the carve-outs of a directory only a glob expansion could…
ronleizrowice-ant Sep 16, 2026
ec07712
test(linux): split the runtime arms out and pin the cases the fixes add
ronleizrowice-ant Sep 16, 2026
9945d57
Pin the bubblewrap 0.12 build in CI, and describe the read-side rules…
ronleizrowice-ant Sep 16, 2026
bde0ee3
Merge remote-tracking branch 'origin/main' into fix/linux-read-deny-g…
ronleizrowice-ant Sep 17, 2026
0e6ddc5
test(linux): assert the collapse's mounts with the shared argv helper
ronleizrowice-ant Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,28 @@ jobs:
if: matrix.os != 'windows'
run: npm test

# bubblewrap 0.12 refuses a mount on a symlink destination, which the
# distribution's packaged bubblewrap accepts; run the suite against it
# as well so a profile that mounts on a link is caught here, not on a
# user's machine. Pinned by commit, not by the v0.12.0 tag, which the
# upstream repository can move.
- name: Build bubblewrap 0.12.0 (Linux)
if: matrix.os == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y -qq meson ninja-build libcap-dev pkg-config
git clone --filter=blob:none https://github.com/containers/bubblewrap.git "$RUNNER_TEMP/bubblewrap"
cd "$RUNNER_TEMP/bubblewrap"
git checkout 2a76602a8c71f36c1527cf9fc3417d9149822e0c
meson setup _build --prefix="$RUNNER_TEMP/bwrap-0.12" -Dselinux=disabled -Dman=disabled -Dbash_completion=disabled -Dzsh_completion=disabled
ninja -C _build
ninja -C _build install
"$RUNNER_TEMP/bwrap-0.12/bin/bwrap" --version

- name: Run tests against bubblewrap 0.12.0 (Linux)
if: matrix.os == 'linux'
run: PATH="$RUNNER_TEMP/bwrap-0.12/bin:$PATH" npm test

- name: Run Node.js fallback tests
if: matrix.os != 'windows'
run: node test/utils/which-node-test.mjs
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,21 @@ Examples:

**Path Syntax (Linux):**

**Linux currently does not support glob matching.** Use literal paths only:
bubblewrap binds concrete paths, so glob support is narrower than on macOS:

- `allowWrite` / `denyWrite` take literal paths. A trailing `/**` is dropped (`src/**` means `src`); any other glob pattern there is skipped.
- `denyRead` / `allowRead` accept the same glob syntax as macOS, expanded to the entries that exist when the command is wrapped, so a file that appears later is not covered. The pattern needs a literal directory to start from (a relative pattern starts at the current directory): one with a wildcard in its first path component, such as `/**/*.pem` or `/opt*/keys/**`, is skipped on Linux. Only directories the pattern can match beneath are listed (`certs/*.pem` lists `certs` alone).
- A directory matched by a `denyRead` pattern ending in `/**` that holds at least one entry when the command is wrapped becomes one tmpfs mount, like a directory listed in `denyRead` literally: inside the sandbox it is an EMPTY WRITABLE directory, so a command that used to write through a read-denied `build/` still writes, into the tmpfs, and loses that output when the command exits. A file added to the directory on the host afterwards is hidden too. A matched directory that is empty when the command is wrapped gets no mount (a matched symlink to a directory always gets one, on the directory it leads to). An `allowRead` beneath a mounted directory is bound back over the tmpfs, but each entry beneath it that the pattern matches keeps its own mask: under a `/**` pattern that is every entry there, so only what is created beneath the `allowRead` later is readable.
- A directory the expansion cannot list is denied as a whole, and nothing is bound back beneath the mount that hides it, `allowRead` and `allowWrite` paths included: what the pattern matches under them cannot be found. A `denyRead` entry that cannot be inspected (its parent directory is readable but not searchable, say), or that leads to `/`, hides the nearest directory above it instead, in the same way.
- Symlinked directories are descended, one spelling per directory. Every `denyRead` mount goes where the path really is (bubblewrap 0.12 and later refuse to mount on a symlink), so an entry reached through a symlink is denied under every name that leads to it, and a link back up the tree denies everything it reaches, as a literal deny of the link would. A link that resolves to nothing is skipped. `allowRead` globs are not expanded through symlinks: they match the link itself.
- An `allowRead` or `allowWrite` path is bound back over a denied directory only where it really is, so no directory shows under a second name inside the sandbox.
- `denyRead: ["/"]` denies each directory in `/` (`/proc`, `/dev` and `/sys` aside); a symlink there (`/bin`, `/lib` on a usr-merged system) gets no mount of its own, because what it leads to is denied together with the directory that holds it.

Examples:

- `"allowWrite": ["src/"]` - Allow write to `src/` directory
- `"denyRead": ["/home/user/.ssh"]` - Deny read to SSH directory
- `"denyRead": ["**/build/**"]` - Deny read to every `build/` directory under the current directory
- `"denyRead": ["/home"], "allowRead": ["."]` - Deny read to all of `/home`, but re-allow the current directory

**All platforms:**
Expand Down
Loading
Loading