Safe MountInfo
Linux /proc/self/mountinfo is the per-process kernel-published mount table. The format is fixed per [kernel Documentation/filesystems/proc.rst §3.5](https://www.kernel.org/doc/Documentation/filesystems/proc.txt):
The field (positional index 3, 0-based) is "root within source FS" — "/" for a regular mount, a non-root path for a bind-mount (e.g. /Users/me/data mounted onto /data inside a container). Bind-mount detection MUST consult this field; ad-hoc parsers that scan the options string for the word "bind" miss the truth (kernel doesn't emit "bind" as an option — bind state is observable ONLY via field 4).
Pre-v0.11.6 the only lib/ caller (lib/watcher.js) parsed mountinfo correctly inline. Future callers — container-escape detection, sealed-store path validation, sandbox auto-probe — would have to re-derive the discipline. This primitive centralizes it: a single canonical parser that ALWAYS reads field 4, ALWAYS handles the " - " optional-fields separator, ALWAYS skips malformed lines without throwing.
Refusal posture: - safe-mount-info/read-failed — /proc/self/mountinfo unreadable (non-Linux, restricted sandbox, host filesystem hidden). Operators get the typed error AND opts.fallback value (default null) to take. - safe-mount-info/parse-failed — single malformed line within /proc/self/mountinfo. Silent-skip (per-line) by default; opts.strict: true upgrades to throw on first malformed line.
Threat model: - **Container-escape detection** (CVE-2019-5736 Docker / CVE-2022-0185 fsconfig / CVE-2024-21626 leaky-vessels) — bind-mount + root-field analysis is the canonical signal. Wrong-field readers (operations on field 5 / 6 / options- indexOf-"bind") miss escape-attempt patterns. - **Sealed-store integrity** — sealed dbs / vault state atop a bind-mounted host directory cross trust boundaries on container restart. Detection requires reading field 4 and matching the mount-point against operator-trusted paths.
Composes: - lib/safe-decompress / lib/audit — operator-supplied audit handle receives system.safe_mount_info.refused events on read-failed and parse-failed (drop-silent — observability emission must not crash the hot path that emitted the event).
RFC / kernel-doc citations: - [Linux Documentation/filesystems/proc.rst §3.5 — /proc/
b.safeMountInfo.parse(text, opts?) #
{
strict: boolean, // default false; throw on malformed line
maxLines: number, // default 4096; cap to bound parser work
}
Parse /proc/self/mountinfo text bytes into structured entries. Each entry carries { id, parent, devMajMin, root, mountPoint, options, fstype, source, superOptions } — root is the positional field 4 ("root within source FS") that bind-mount detection requires.
Malformed lines are skipped by default (operator's mountinfo MAY contain a stray line during a concurrent mount/unmount). Set opts.strict: true to throw on first malformed line.
var entries = b.safeMountInfo.parse(rawText);
var bindMounts = entries.filter(function (e) { return e.root !== "/"; });
b.safeMountInfo.read(opts?) #
{
path: string, // override path; on Linux defaults to /proc/self/mountinfo, elsewhere there is no default
fallback: any, // returned on read failure (default null)
audit: object, // optional b.audit handle for refusal events
strict: boolean, // forwarded to parse()
maxLines: number, // forwarded to parse()
}
Read + parse /proc/self/mountinfo in one call. Returns the same array shape as parse(text).
The default path is used on Linux only. Elsewhere there is no default and opts.fallback (default null) is returned without reading anything, with an audit emission of safe-mount-info.refused carrying code no-default-path. /proc is not simply absent off Linux — a leading slash is drive-relative on Windows, so the default names C:\proc\self\mountinfo, which an unprivileged local user can create; reading it would let whoever wrote it decide which filesystem a path appears to be on. A caller holding a mountinfo file elsewhere passes opts.path, which is honored on every platform and reports a read failure as read-failed.
var entries = b.safeMountInfo.read();
if (entries === null) {
// non-Linux / sandboxed / no /proc
}
b.safeMountInfo.bestMatch(entries, path) #
Find the mountinfo entry whose mountPoint is the longest prefix of path. Returns null when no entry covers path. The "longest prefix" semantic is what bind-mount detection / sealed-store-path validation needs — a mounted subdir wins over the root mount.
var entries = b.safeMountInfo.read();
var atPath = b.safeMountInfo.bestMatch(entries, "/var/lib/blamejs");
if (atPath && atPath.root !== "/") {
// path lives on a bind-mount (potentially crossing host/guest)
}
b.safeMountInfo.isBindMount(entry) #
true when the mountinfo entry's root field is something other than "/" (i.e. the mount is a bind from a non-root path within the source filesystem). The canonical bind-mount test — does NOT consult the options string (the kernel doesn't emit "bind" there).
var entries = b.safeMountInfo.read();
var atData = b.safeMountInfo.bestMatch(entries, "/data");
var isBind = b.safeMountInfo.isBindMount(atData);