Bind protected update commands to their source root
This commit is contained in:
1 parent
5b692c5b30
commit
f37c73fa20
6 files changed
+84
-1
No files matched your search
@@ -12,6 +12,7 @@ source "${BASH_SOURCE[0]%/*}/omarchy-security-functions" || exit 126
|
||||
omarchy_security_require_privileged_bash_startup || exit 126
|
||||
set -e
|
||||
omarchy_security_sanitize_bash_environment "$0" "$@"
|
||||
omarchy_security_require_source_root "$0"
|
||||
user_path=$PATH
|
||||
omarchy_security_revoke_sudo_timestamp || exit 1
|
||||
omarchy_security_install_sudo_cleanup_traps
|
||||
|
||||
@@ -44,6 +44,20 @@ omarchy_security_sanitize_bash_environment() {
|
||||
fi
|
||||
}
|
||||
|
||||
omarchy_security_require_source_root() {
|
||||
local command_source command_name=${1##*/}
|
||||
command_source=$(/usr/bin/readlink -e -- "$1") || return 1
|
||||
|
||||
# A runtime root selects the code used by this invocation. Accept the
|
||||
# canonical checkout containing the entrypoint or the package's bin links.
|
||||
if [[ $OMARCHY_PATH != /* || $(/usr/bin/realpath -e -- "$OMARCHY_PATH") != "$OMARCHY_PATH" ]] ||
|
||||
! { [[ $command_source == "$OMARCHY_PATH/bin/$command_name" ]] ||
|
||||
[[ $OMARCHY_PATH == "/usr/share/omarchy" && $command_source == "/usr/bin/$command_name" ]]; }; then
|
||||
echo "OMARCHY_PATH does not match this Omarchy command." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
omarchy_security_sudo_supports_no_update() {
|
||||
local help
|
||||
help=$(LC_ALL=C /usr/bin/sudo -h 2>&1) || return 1
|
||||
|
||||
@@ -14,6 +14,7 @@ source "${BASH_SOURCE[0]%/*}/omarchy-security-functions" || exit 126
|
||||
omarchy_security_require_privileged_bash_startup || exit 126
|
||||
set -e
|
||||
omarchy_security_sanitize_bash_environment "$0" "$@"
|
||||
omarchy_security_require_source_root "$0"
|
||||
# Logging and lock acquisition re-exec this command with a sanitized PATH.
|
||||
# Preserve the caller's path only for the later unprivileged hook/mise phases.
|
||||
user_path=${OMARCHY_UPDATE_USER_PATH:-$PATH}
|
||||
|
||||
@@ -13,6 +13,7 @@ source "${BASH_SOURCE[0]%/*}/omarchy-security-functions" || exit 126
|
||||
omarchy_security_require_privileged_bash_startup || exit 126
|
||||
set -e
|
||||
omarchy_security_sanitize_bash_environment "$0" "$@"
|
||||
omarchy_security_require_source_root "$0"
|
||||
omarchy_security_revoke_sudo_timestamp || exit 1
|
||||
omarchy_security_install_sudo_cleanup_traps
|
||||
omarchy_security_enable_no_update_sudo
|
||||
|
||||
@@ -144,11 +144,12 @@ omarchy-update
|
||||
|
||||
Important behavior:
|
||||
|
||||
- `omarchy update` uses the session’s `OMARCHY_PATH` and a fixed command search path for its system phases. User PATH is restored behind the sudo wrapper for hooks and mise.
|
||||
- Protected update entrypoints require the session's canonical `OMARCHY_PATH` to match their own checkout or the packaged `/usr/bin` entrypoint before selecting commands or the sudo wrapper. This preserves intentionally trusted development checkouts while rejecting a command paired with a different source root. System phases use a fixed command search path; user PATH is restored behind the sudo wrapper for hooks and mise.
|
||||
- Mixed-trust update entrypoints start Bash in privileged mode, discard `BASH_ENV`, `ENV`, and exported-function records before launching helpers, and reject an ordinary `bash path/to/command` invocation. Run them as executables (normally through the `omarchy` CLI); `/usr/bin/bash -p path/to/command` is the explicit interpreter form. This keeps shell startup injection from replacing the no-update sudo boundary.
|
||||
- In dev-link mode, `omarchy update` fast-forwards the active checkout from its configured upstream before changing system packages or running migrations.
|
||||
- Migrations remain in chronological order even though historical entries mix user-controlled code with later privileged repairs. Before entering that mixed-trust tail, Omarchy invalidates its timestamp and forces every later sudo call—including AUR's configurable sudo command—to use `--no-update`; prompts authorize one command without publishing a reusable timestamp. Yay's credential loop is disabled for the update.
|
||||
- User-controlled post-update hooks and mise tools run only after every sudo-capable update stage. Omarchy invalidates its sudo timestamp before each boundary and on every exit; detached children therefore have no later reusable update authorization to wait for.
|
||||
- This lifecycle controls authorization created by the protected workflow. `sudo -N` prevents cache updates but can use an existing valid credential, and `sudo -k` revokes the current session's timestamp. It does not isolate the account from unrelated concurrent authentication in another workflow.
|
||||
- `-y` exports `OMARCHY_UPDATE_UNATTENDED=1` and suppresses Omarchy confirmation prompts. Interactive review steps (orphan removal, conflict handoff) report and skip instead of blocking. Privileged commands still require sudo authorization, and command-scoped authentication can prompt separately for each command.
|
||||
- The free-space requirement uses a 10 GiB threshold and stops the update before
|
||||
confirmation when it is not met. If free space cannot be determined, the
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
source "$SHELL_TEST_DIR/fixtures/sudo-boundary-test.sh"
|
||||
source "$SUDO_TEST_ROOT/bin/omarchy-security-functions"
|
||||
|
||||
rm -f "$SUDO_TEST_ROOT/bin/omarchy-update"
|
||||
copy_boundary_file bin/omarchy-update
|
||||
omarchy_security_require_source_root "$SUDO_TEST_ROOT/bin/omarchy-update" || fail "matching checkout root was rejected"
|
||||
pass "a canonical checkout matches its own entrypoint"
|
||||
|
||||
mkdir "$boundary_tmp/other-root"
|
||||
ln -s "$SUDO_TEST_ROOT" "$boundary_tmp/root-link"
|
||||
for root in "$boundary_tmp/other-root" "$boundary_tmp/root-link" .; do
|
||||
if OMARCHY_PATH="$root" omarchy_security_require_source_root "$SUDO_TEST_ROOT/bin/omarchy-update" >"$boundary_tmp/output" 2>&1; then
|
||||
fail "a different or noncanonical source root was accepted"
|
||||
fi
|
||||
done
|
||||
pass "different, symlink and relative roots are rejected"
|
||||
|
||||
# Redirect only the two package-layout literals into the fixture. Resolution
|
||||
# still uses real readlink/realpath; no host /usr/bin file is changed or run.
|
||||
package_root="$boundary_tmp/usr/share/omarchy"
|
||||
package_bin="$boundary_tmp/usr/bin"
|
||||
mkdir -p "$package_root/bin" "$package_bin"
|
||||
cp "$SUDO_TEST_ROOT/bin/omarchy-update" "$package_bin/omarchy-update"
|
||||
cp "$SUDO_TEST_ROOT/bin/omarchy-update" "$package_bin/different-command"
|
||||
ln -s "$package_bin/omarchy-update" "$package_root/bin/omarchy-update"
|
||||
python3 - "$SUDO_TEST_ROOT/bin/omarchy-security-functions" "$boundary_tmp/package-library" "$package_root" "$package_bin" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
source, output, root, binaries = sys.argv[1:]
|
||||
text = Path(source).read_text()
|
||||
text = text.replace('"/usr/share/omarchy"', f'"{root}"')
|
||||
text = text.replace('"/usr/bin/$command_name"', f'"{binaries}/$command_name"')
|
||||
Path(output).write_text(text)
|
||||
PY
|
||||
source "$boundary_tmp/package-library"
|
||||
OMARCHY_PATH="$package_root" omarchy_security_require_source_root "$package_bin/omarchy-update" || fail "package binary was rejected"
|
||||
OMARCHY_PATH="$package_root" omarchy_security_require_source_root "$package_root/bin/omarchy-update" || fail "package link was rejected"
|
||||
pass "the package binary and its matching source-tree link are accepted"
|
||||
|
||||
ln -sfn "$package_bin/different-command" "$package_root/bin/omarchy-update"
|
||||
if OMARCHY_PATH="$package_root" omarchy_security_require_source_root "$package_root/bin/omarchy-update" >"$boundary_tmp/output" 2>&1; then
|
||||
fail "a package link to a different command was accepted"
|
||||
fi
|
||||
pass "a package link must resolve to its named command"
|
||||
|
||||
# Run the protected entrypoints themselves with a mismatched root. These must
|
||||
# stop before any sudo or operational fixture command, not merely validate in
|
||||
# an isolated library test.
|
||||
for command in omarchy-update omarchy-refresh-pacman omarchy-update-stay-awake; do
|
||||
rm -f "$SUDO_TEST_ROOT/bin/$command"
|
||||
copy_boundary_file "bin/$command"
|
||||
for root in "$boundary_tmp/other-root" .; do
|
||||
reset_boundary
|
||||
if OMARCHY_PATH="$root" "$SUDO_TEST_ROOT/bin/$command" >"$boundary_tmp/output" 2>&1; then
|
||||
fail "$command accepted a mismatched root"
|
||||
fi
|
||||
[[ ! -s $SUDO_TEST_LOG ]] || fail "$command ran work before rejecting its root"
|
||||
done
|
||||
pass "$command rejects mismatched and relative roots before work"
|
||||
done
|
||||
Reference in new issue
Block a user