From 47f1865f7ed37994d1cc6002760082edf9a52466 Mon Sep 17 00:00:00 2001 From: "Stuart B. Wilkins" Date: Fri, 30 Oct 2026 04:58:15 -0400 Subject: [PATCH] fix(bwrap): resolve symlinks and check overlap of mount sources The check for mounts that expose the N2C session directory compared the source as written, so a symlinked source that leads to a directory holding it was missed. Compare the source as written and resolved against the session directory as written and resolved, in both directions (contains or lies inside). Assisted-by: opencode:claude-sonnet-5-5 --- docs/bw-wrappers.md | 6 ++++-- lib/bwrap_sandbox_lib.sh | 29 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/bw-wrappers.md b/docs/bw-wrappers.md index e136490..5667ac9 100644 --- a/docs/bw-wrappers.md +++ b/docs/bw-wrappers.md @@ -126,8 +126,10 @@ NSLS-II API in `/run/user//nsls2/creds//token`, for example - **Automatic mounts cannot expose it either.** Directories the launcher mounts whole (the npm and pixi caches, ccache, the tool data and cache directories) are only checked for being blocked themselves. So, after all mounts are built, - the launch stops with an error if any bind mount would contain the N2C session - directory, for example with `N2C_STATE_DIR` set inside `~/.npm`. The default + the launch stops with an error if any bind mount's source (as written or with + symlinks resolved) is, contains or lies inside the N2C session directory, for + example with `N2C_STATE_DIR` set inside `~/.npm`, or `~/.npm` being a symlink to + a directory that holds it. The default location, under `/run/user/`, is never inside such a directory. - **The directories are checked.** The creds directory and each token directory must be real directories (not symlinks), owned by you, with mode `0700`, and diff --git a/lib/bwrap_sandbox_lib.sh b/lib/bwrap_sandbox_lib.sh index 9931e4f..4825122 100644 --- a/lib/bwrap_sandbox_lib.sh +++ b/lib/bwrap_sandbox_lib.sh @@ -1717,27 +1717,36 @@ build_n2c_token_mounts() { # (npm and pixi caches, ccache, tool data and cache directories) are only # checked for being blocked themselves, so with N2C_STATE_DIR set inside one of # them the session directory would be mounted along with it. This looks at the -# finished mount list, so it covers every mount whatever added it. Nothing is -# checked while the directory does not exist. +# finished mount list, so it covers every mount whatever added it. A bind +# counts when its source, as written or with symlinks resolved, is the session +# directory, contains it or lies inside it, compared against the session +# directory as written and resolved. Nothing is checked while the directory +# does not exist. # # Called by print_dry_run and launch_sandbox, after all mounts are built. verify_n2c_state_not_exposed() { - local state canon src flag i=0 + local state state_canon src src_canon flag s t i=0 state="$(_n2c_state_dir)" [[ -e "${state}" ]] || return 0 - canon="$(readlink -f -- "${state}" 2> /dev/null || true)" + state_canon="$(readlink -f -- "${state}" 2> /dev/null || true)" while [[ ${i} -lt ${#BWRAP_ARGS[@]} ]]; do flag="${BWRAP_ARGS[${i}]}" _bwrap_flag_arity "${flag}" case "${flag}" in --bind | --bind-try | --ro-bind | --ro-bind-try | --dev-bind | --dev-bind-try) src="${BWRAP_ARGS[$((i + 1))]}" - if _is_prefix_of "${src}" "${state}" || - { [[ -n "${canon}" ]] && _is_prefix_of "${src}" "${canon}"; }; then - echo "Error: the mount of '${src}' would expose the N2C session directory '${state}' (it holds the refresh token)." >&2 - echo " Move N2C_STATE_DIR out of directories that the launcher mounts (for example ~/.npm, ~/.pixi, ~/.ccache)." >&2 - exit 1 - fi + src_canon="$(readlink -f -- "${src}" 2> /dev/null || true)" + for s in "${src}" "${src_canon}"; do + [[ -n "${s}" ]] || continue + for t in "${state}" "${state_canon}"; do + [[ -n "${t}" ]] || continue + if _is_prefix_of "${s}" "${t}" || _is_prefix_of "${t}" "${s}"; then + echo "Error: the mount of '${src}' would expose the N2C session directory '${state}' (it holds the refresh token)." >&2 + echo " Move N2C_STATE_DIR out of directories that the launcher mounts (for example ~/.npm, ~/.pixi, ~/.ccache)." >&2 + exit 1 + fi + done + done ;; esac i=$((i + 1 + _BWRAP_FLAG_ARITY))