Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ Examples:
- `allowPty` - macOS only: allow pseudo-terminal operations (boolean, default: false). The seatbelt profile then carries `(allow pseudo-tty)` plus read, write and `ioctl` on `/dev/ptmx` and `/dev/ttys*`, which a command that allocates a pty of its own needs. Not read on Linux or Windows.
- `bwrapPath` - Linux only: absolute path to the `bwrap` (bubblewrap) binary, used instead of resolving `bwrap` on `PATH`. A path that is not executable is a dependency error, and no `PATH` lookup is tried after it.
- `socatPath` - Linux only: absolute path to the `socat` binary, used instead of resolving `socat` on `PATH`, with the same rule for a path that is not executable.
- `seccomp` - Linux only: `applyPath` points at the `apply-seccomp` binary to use instead of the packaged one, which is still used when nothing exists at that path. `argv0` invokes it as a multicall binary that dispatches on the `ARGV0` environment variable; `applyPath` is then used verbatim (no existence check) and must resolve inside the bubblewrap namespace.
- `seccomp` - Linux only: `applyPath` points at the `apply-seccomp` binary to use instead of the packaged one, which is still used when nothing exists at that path. `argv0` invokes it as a multicall binary that dispatches on the `ARGV0` environment variable; `applyPath` is then used verbatim (no existence check) and must resolve inside the bubblewrap namespace. `holdsLinks` says whether the helper takes `--hold-link` (see "Denied paths and symbolic links"), and is taken at its word: nothing is run to find out. Not set, a helper the library found for itself is read for the option's name, and one named by `applyPath` is handed no link. Set it from a probe of your own helper (`apply-seccomp --holds-links` exits 0), not from a constant: a helper without the option takes `--hold-link` for the command, so under a wrong `true` no sandboxed command starts where there is a link to hold.
- `ripgrep` - Linux only: how to invoke ripgrep for the deny-path scan (default: `{ "command": "rg" }`). `args` are passed before srt's own arguments, and `argv0` overrides `argv[0]` for a multicall binary.
- `git.safeDirectories` - Directories git should treat as `safe.directory` inside the sandbox, where the working tree is owned by another user — the repository top level when the command runs from a subdirectory, say. Injected through `GIT_CONFIG_*` environment variables, and it grants no write access of its own.
- `credentials` - Environment variables (`credentials.envVars`) and files (`credentials.files`) the sandboxed command must not read the real values of. `mode: "deny"` withholds the value; `mode: "mask"` puts a sentinel in its place inside the sandbox and has the proxy substitute the real value back on egress to the hosts that entry's `injectHosts` lists (default: `network.allowedDomains`). Masking requires `network.tlsTerminate`, so the real value only leaves over a verified TLS connection, unless `credentials.allowPlaintextInject` opts out. Files are masked on Linux only; macOS denies a masked file instead.
Expand Down Expand Up @@ -1172,6 +1172,18 @@ Filesystem restrictions are enforced at the OS level:

**Writes through links (Linux and macOS):** a write is judged by where it lands, after symbolic links are followed. A link inside an allowed write path that leads out of it gives a command nothing: the write is refused. A link that leads into an allowed write path works like any other name for that place. A hard link is a second name for the file itself: on Linux a file outside the allowed write paths that already has a hard link inside one can be written through it, and a sandboxed command cannot create such a link. On macOS nothing is promised for such a file.

**Denied paths and symbolic links (Linux and macOS):** a `denyWrite` entry or a [mandatory deny path](#mandatory-deny-paths-auto-protected-files) with a symbolic link on the way to it, its last name included, denies the path the links lead to, whether or not anything is there yet, and keeps each link where it is: removing, renaming and replacing it are refused, and so is renaming a directory above it. A directory that a link's target goes into and out of again by `..` is kept in place too, and a name that is not there and that the target goes up from cannot be made. The link reads and leads as before, and the rest of its directory stays as writable as it was. The links are those found when the command is wrapped.

- Nothing is denied among the devices: a name blanked with a link to `/dev/null` is kept, and `/dev/null` stays as it was.
- A tool that replaces a file instead of writing into it (`sed -i`, `mv -f`, `ln -sfn`, `rsync`, `tar x`, `git checkout` where the tracked link differs, `git clean -fdx`) is refused at such a name, as at any denied one, on Linux with `EBUSY` ("Device or resource busy").
- A directory on the way that cannot be looked in (no search permission, or a look that fails twice for any reason but that nothing is or can be at the name) is denied whole in place of the path, its mode included. Never `/`: there the path is taken as written.
- On Linux the same links are kept on the way to a `denyRead` entry, to a link that a `denyRead` pattern matches, and to a `credentials.files` path: the next command's mounts are placed by the name again.
- On Linux bubblewrap cannot mount on a link, so the seccomp helper does (`apply-seccomp --hold-link <path> <target>`): each link in a directory the command can write to is mounted on itself, read-only, in the mount namespaces made for the command. Where that cannot be done (the name is no link any more, the link says something else than when the command was wrapped, the mount is refused), no command is started: the helper exits 1, and its one line on stderr starts with `apply-seccomp: --hold-link <path>: `. `seccomp.holdsLinks: false` turns holding off.
- On Linux `SandboxManager.getLinuxUnheldLinks()` returns each link that should be held and is not, as `{ path, reason }`, to show or to look at again after the command; the only other sign is a line under `SRT_DEBUG`. What such a link leads to is denied all the same, and the command can replace the link. The reasons: `no_helper` (`allowAllUnixSockets`, or no binary for the architecture), `told_no` (`seccomp.holdsLinks` is false), `not_told` (the helper is named by `seccomp.applyPath` and `seccomp.holdsLinks` is not set), `older_helper` (the helper the library found for itself is built from older sources), `no_answer` (that helper's file could not be read). A link is listed by the latest wrap that met it. `reset()` and `updateConfig()` empty the list, and a wrap still in flight then lists its links afterwards.
- On Linux the held links' paths and targets stay on the command line (see the note on large profiles below): some thousand of them reach the limit of one argument, and the `command_too_long` refusal says how many bytes are theirs.
- Not covered: a mandatory deny name _below_ the working directory that is itself a link (the Linux scan does not see it; the macOS pattern keeps the name and says nothing of what it leads to). On Linux, a link on the way to what a `denyRead` pattern matches, other than the match itself (a `denyWrite` pattern is skipped there as a whole), and a writer outside the sandbox that changes a link while the command is being wrapped.
- Not reported on Linux: the violation monitor judges by the configured names, so a refused attempt on a kept link or directory is no violation in its store.

**Read-side rules (Linux):** an entry is matched by the name it is, not by what it points at.

- An `allowRead` entry re-allows the name it names, so a link planted at an allowed path cannot re-open a denied one. One that is itself a symlink is bound back at its own name, from the target it was checked against: the target's own path stays hidden, and the target's contents are reachable only through the name. A `denyRead` entry or a credential mask that overlaps that target — at it, inside it, or covering it — wins over the carve-out, which is then not restored at all: the name is absent inside the sandbox rather than serving what the deny hides. The denied directory the carve-out is being restored into is not such an overlap, nor is anything above it: those are what the carve-out is an exception to. Neither is an entry that denies nothing — one naming a path that is not there, or a file an `allowRead` entry lifts.
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export {
type SandboxDependencyCheck,
LinuxSandboxProfileError,
type LinuxSandboxProfileErrorCode,
type LinuxUnheldLink,
type LinuxUnheldLinkReason,
} from './sandbox/linux-sandbox-utils.js'

// Windows install/status API
Expand Down
Loading
Loading