OM-SEC-01: Make passwordless sudo expiry fail closed

This commit is contained in:
Afonso Oliveira committed 2026-09-01 10:40:46 +01:00
1 parent 945af75aa2
commit 87625c2ad8
6 files changed
+955 -148

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Provide internal fail-closed helpers for security-sensitive commands
# Shared fail-closed primitives for security-sensitive Omarchy commands. This
# file is sourced from the same package-owned bin directory as its consumers.
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
echo "omarchy-security-functions is an internal function library." >&2
exit 64
fi
omarchy_security_require_privileged_bash_startup() {
local pid=${1:-$$}
[[ $- == *p* && $pid =~ ^[1-9][0-9]*$ ]] || return 1
/usr/bin/env -i /usr/bin/bash -p -c '
mapfile -d "" -t argv <"/proc/$1/cmdline" || exit 1
executable=$(/usr/bin/readlink -e -- "/proc/$1/exe") || exit 1
[[ $executable == /usr/bin/bash ]]
[[ ${argv[0]:-} == /bin/bash || ${argv[0]:-} == /usr/bin/bash ]]
[[ ${argv[1]:-} == -p ]]
' omarchy-bash-startup "$pid"
}
omarchy_security_sudo_supports_no_update() {
LC_ALL=C /usr/bin/sudo -h 2>&1 |
/usr/bin/grep -Eq '^usage: sudo .*\[[^]]*N[^]]*\]'
}
omarchy_security_revoke_sudo_timestamp() {
/usr/bin/sudo -k >/dev/null 2>&1
}
omarchy_security_exit_with_revoked_sudo() {
local status=$1
local message=${2:-Could not invalidate cached sudo authorization.}
trap - EXIT HUP INT TERM
if ! omarchy_security_revoke_sudo_timestamp; then
echo "$message" >&2
(( status != 0 )) || status=1
fi
exit "$status"
}
omarchy_security_install_signal_exit_traps() {
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
}
omarchy_security_run_sudo_cleanup_trap() {
local status=$?
omarchy_security_exit_with_revoked_sudo "$status" \
"${OMARCHY_SECURITY_SUDO_CLEANUP_MESSAGE:-Could not invalidate cached sudo authorization.}"
}
omarchy_security_install_sudo_cleanup_traps() {
OMARCHY_SECURITY_SUDO_CLEANUP_MESSAGE=${1:-Could not invalidate cached sudo authorization.}
trap omarchy_security_run_sudo_cleanup_trap EXIT
omarchy_security_install_signal_exit_traps
}
omarchy_security_assert_root_directory() {
local path=$1 expected_mode=$2 canonical owner actual_mode
[[ $path == /* && -d $path && ! -L $path ]] || return 1
canonical=$(/usr/bin/realpath -e -- "$path") || return 1
[[ $canonical == "$path" ]] || return 1
read -r owner actual_mode < <(/usr/bin/stat -Lc '%u %a' -- "$path") || return 1
[[ $owner == "0" && $actual_mode == "$expected_mode" ]]
}
omarchy_security_prepare_private_root_directory() {
local path=$1 parent=$2
omarchy_security_assert_root_directory "$parent" 755 || return 1
if [[ -e $path || -L $path ]]; then
omarchy_security_assert_root_directory "$path" 700
else
/usr/bin/install -d -o root -g root -m 0700 -- "$path" || return 1
omarchy_security_assert_root_directory "$path" 700
fi
}
+451 -40
View File
@@ -1,70 +1,481 @@
#!/bin/bash
#!/bin/bash -p
# omarchy:summary=Toggle passwordless sudo for the current user.
# omarchy:args=[MINUTES]
# omarchy:requires-sudo=true
NOPASSWD_FILE="/etc/sudoers.d/99-omarchy-nopasswd-${USER}"
TIMER_NAME="omarchy-nopasswd-expire-${USER}"
source "${BASH_SOURCE[0]%/*}/omarchy-security-functions" || exit 126
MINUTES=${1:-15}
if [[ $1 && ! $1 =~ ^[0-9]+$ ]]; then
echo "Usage: omarchy-sudo-passwordless [MINUTES]" >&2
exit 1
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
omarchy_security_require_privileged_bash_startup || {
echo "Refusing an unsafe Bash startup for passwordless sudo." >&2
exit 126
}
unset BASH_ENV ENV
fi
arm_expiry() {
if sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
rm -f -- "$NOPASSWD_FILE"; then
return 0
fi
set -euo pipefail
echo "Failed to schedule passwordless sudo expiry. Revoking access now." >&2
if ! sudo rm -f -- "$NOPASSWD_FILE"; then
echo "CRITICAL: Could not remove $NOPASSWD_FILE. Remove it as root immediately." >&2
readonly DEFAULT_MINUTES=15
readonly MAX_MINUTES=1440
readonly STATE_DIR=/var/lib/omarchy/sudo-passwordless
readonly RUNTIME_DIR=/run/omarchy/sudo-passwordless
readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock
readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf
readonly INSTALLED_SELF=/usr/bin/omarchy-sudo-passwordless
usage() {
echo "Usage: omarchy-sudo-passwordless [MINUTES]" >&2
echo "MINUTES must be between 1 and $MAX_MINUTES." >&2
exit 1
}
valid_minutes() {
[[ $1 =~ ^[0-9]+$ ]] && ((10#$1 >= 1 && 10#$1 <= MAX_MINUTES))
}
valid_uid() {
[[ $1 =~ ^[0-9]+$ ]] && ((10#$1 >= 1 && 10#$1 <= 4294967294))
}
valid_account_name() {
[[ $1 =~ ^[a-z_][a-z0-9_-]{0,31}$ ]]
}
resolve_account() {
local uid="$1" entry
valid_uid "$uid" || return 1
entry=$(/usr/bin/getent passwd "$((10#$uid))") || return 1
IFS=: read -r ACCOUNT_NAME _ ACCOUNT_UID _ _ _ _ <<<"$entry"
[[ $ACCOUNT_UID == "$((10#$uid))" ]] || return 1
# Sudoers names and the legacy filename both have metacharacters. Omarchy
# accounts use this portable subset; refusing anything else is safer than
# attempting to quote privileged policy syntax.
valid_account_name "$ACCOUNT_NAME" || return 1
ACCOUNT_UID=$((10#$uid))
}
verify_sudo_caller() {
local requested_uid="$1"
((EUID == 0)) || return 1
valid_uid "$requested_uid" || return 1
[[ ${SUDO_UID:-} =~ ^[0-9]+$ ]] || return 1
((10#$SUDO_UID == 10#$requested_uid)) || return 1
resolve_account "$requested_uid"
}
prepare_root_state() {
omarchy_security_assert_root_directory /var 755 || return 1
[[ -d /var/lib && ! -L /var/lib ]] || return 1
[[ $(/usr/bin/stat -Lc '%u' /var/lib) == 0 ]] || return 1
! ((8#$(/usr/bin/stat -Lc '%a' /var/lib) & 022)) || return 1
if [[ ! -e /var/lib/omarchy && ! -L /var/lib/omarchy ]]; then
/usr/bin/install -d -o root -g root -m 0755 /var/lib/omarchy || return 1
fi
return 1
omarchy_security_assert_root_directory /var/lib/omarchy 755 || return 1
omarchy_security_prepare_private_root_directory "$STATE_DIR" /var/lib/omarchy || return 1
omarchy_security_assert_root_directory /run 755 || return 1
if [[ ! -e /run/omarchy && ! -L /run/omarchy ]]; then
/usr/bin/install -d -o root -g root -m 0755 /run/omarchy || return 1
fi
omarchy_security_assert_root_directory /run/omarchy 755 || return 1
omarchy_security_prepare_private_root_directory "$RUNTIME_DIR" /run/omarchy
}
with_root_lock() {
local fd rc=0
# The boot cleanup cannot depend on STATE_DIR or RUNTIME_DIR being healthy:
# those are exactly the kinds of partial-install state it must fail closed
# through. /run/lock is established by the OS before sysinit services run.
omarchy_security_assert_root_directory /run 755 || return 1
[[ -d /run/lock && ! -L /run/lock ]] || return 1
[[ $(/usr/bin/stat -Lc '%u' /run/lock) == 0 ]] || return 1
! ((8#$(/usr/bin/stat -Lc '%a' /run/lock) & 022)) || return 1
exec {fd}>"$LOCK_FILE" || return 1
/usr/bin/chown root:root "$LOCK_FILE" || return 1
/usr/bin/chmod 0600 "$LOCK_FILE" || return 1
/usr/bin/flock -x "$fd" || return 1
"$@" || rc=$?
/usr/bin/flock -u "$fd" || rc=1
exec {fd}>&-
return "$rc"
}
rule_file() {
printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$1"
}
state_file() {
printf '%s/%s.state' "$STATE_DIR" "$1"
}
read_state_record() {
local uid="$1" file state_uid name expires timer canonical_uid
local -a lines=()
valid_uid "$uid" || return 1
canonical_uid=$((10#$uid))
file=$(state_file "$uid")
[[ -f $file && ! -L $file ]] || return 1
mapfile -t lines <"$file" || return 1
(( ${#lines[@]} == 4 )) || return 1
[[ ${lines[0]} == UID=* && ${lines[1]} == USER=* &&
${lines[2]} == EXPIRES=* && ${lines[3]} == TIMER=* ]] || return 1
state_uid=${lines[0]#UID=}
name=${lines[1]#USER=}
expires=${lines[2]#EXPIRES=}
timer=${lines[3]#TIMER=}
[[ $state_uid == "$canonical_uid" ]] || return 1
valid_account_name "$name" || return 1
[[ $expires =~ ^[1-9][0-9]{0,10}$ ]] || return 1
[[ $timer =~ ^omarchy-nopasswd-expire-${canonical_uid}-[0-9a-f]{32}$ ]] || return 1
printf '%s\t%s\t%s' "$name" "$expires" "$timer"
}
read_state_timer() {
local record
record=$(read_state_record "$1") || return 1
printf '%s' "${record##*$'\t'}"
}
current_epoch() {
local now
now=$(/usr/bin/date +%s) || return 1
[[ $now =~ ^[1-9][0-9]{0,10}$ ]] || return 1
printf '%s' "$now"
}
valid_expiry() {
[[ $1 =~ ^[1-9][0-9]{0,10}$ ]]
}
valid_timer_for_uid() {
local uid="$1" timer="$2"
valid_uid "$uid" || return 1
uid=$((10#$uid))
[[ $timer =~ ^omarchy-nopasswd-expire-${uid}-[0-9a-f]{32}$ ]]
}
stop_timer() {
local timer="$1"
[[ $timer =~ ^omarchy-nopasswd-expire-[0-9]+-[0-9a-f]{32}$ ]] || return 0
/usr/bin/systemctl stop "${timer}.timer" "${timer}.service" >/dev/null 2>&1 || true
/usr/bin/systemctl reset-failed "${timer}.timer" "${timer}.service" >/dev/null 2>&1 || true
}
classify_generated_rule() {
local file=$1 suffix contents name
GENERATED_RULE_LEGACY_TIMER=""
[[ -f $file && ! -L $file ]] || return 1
contents=$(/usr/bin/cat -- "$file") || return 2
suffix=${file##*/99-omarchy-nopasswd-}
if [[ $suffix =~ ^[0-9]+$ ]]; then
name=${contents%' ALL=(ALL) NOPASSWD: ALL'}
valid_account_name "$name" && [[ $contents == "$name ALL=(ALL) NOPASSWD: ALL" ]]
elif valid_account_name "$suffix" && [[ $contents == "$suffix ALL=(ALL) NOPASSWD: ALL" ]]; then
GENERATED_RULE_LEGACY_TIMER="omarchy-nopasswd-expire-${suffix}"
else
return 1
fi
}
remove_known_legacy_rules() {
local file classification failed=0
shopt -s nullglob
for file in /etc/sudoers.d/99-omarchy-nopasswd-*; do
if classify_generated_rule "$file"; then
# A crash after publishing the numeric rule but before its state rename
# must not survive the next boot. Do not require the account to still
# exist: a deleted account could otherwise make the rule immortal and a
# later username reuse could activate it again.
/usr/bin/rm -f -- "$file" || failed=1
[[ -z $GENERATED_RULE_LEGACY_TIMER ]] ||
/usr/bin/systemctl stop "${GENERATED_RULE_LEGACY_TIMER}.timer" \
"${GENERATED_RULE_LEGACY_TIMER}.service" >/dev/null 2>&1 || true
else
classification=$?
# An unreadable candidate cannot be proven inert. A symlink, non-file,
# or administrator-authored body is unrelated and remains untouched.
(( classification == 1 )) || failed=1
fi
done
shopt -u nullglob
return "$failed"
}
cleanup_uid_locked() {
local uid="$1" timer=""
valid_uid "$uid" || return 1
timer=$(read_state_timer "$uid" 2>/dev/null || true)
# Remove policy first. A failed timer stop can only leave an inert cleanup
# job behind, never extend passwordless access.
/usr/bin/rm -f -- "$(rule_file "$uid")" || return 1
/usr/bin/rm -f -- "$(state_file "$uid")" || return 1
[[ -z $timer ]] || stop_timer "$timer"
}
cleanup_all_locked() {
local state uid failed=0 file classification
shopt -s nullglob
for state in "$STATE_DIR"/*.state; do
uid=${state##*/}
uid=${uid%.state}
if valid_uid "$uid" && ! cleanup_uid_locked "$uid"; then failed=1; fi
done
shopt -u nullglob
remove_known_legacy_rules || failed=1
# Never report a successful boot cleanup while an exact rule emitted by any
# Omarchy implementation is still active. Administrator-extended files do
# not match these complete bodies and remain untouched.
shopt -s nullglob
for file in /etc/sudoers.d/99-omarchy-nopasswd-*; do
if classify_generated_rule "$file"; then
failed=1
else
classification=$?
(( classification == 1 )) || failed=1
fi
done
shopt -u nullglob
return "$failed"
}
verify_boot_cleanup() {
local owner mode canonical current active_rules
[[ -f $BOOT_CLEANUP_FILE && ! -L $BOOT_CLEANUP_FILE ]] || return 1
canonical=$(/usr/bin/realpath -e -- "$BOOT_CLEANUP_FILE") || return 1
[[ $canonical == "$BOOT_CLEANUP_FILE" ]] || return 1
owner=$(/usr/bin/stat -Lc '%u' -- "$BOOT_CLEANUP_FILE") || return 1
mode=$(/usr/bin/stat -Lc '%a' -- "$BOOT_CLEANUP_FILE") || return 1
[[ $owner == 0 && $mode =~ ^[0-7]+$ ]] && ! ((8#$mode & 022)) || return 1
current=${BOOT_CLEANUP_FILE%/*}
while :; do
[[ -d $current && ! -L $current ]] || return 1
canonical=$(/usr/bin/realpath -e -- "$current") || return 1
[[ $canonical == "$current" ]] || return 1
read -r owner mode < <(/usr/bin/stat -Lc '%u %a' -- "$current") || return 1
[[ $owner == 0 && $mode =~ ^[0-7]+$ ]] && ! ((8#$mode & 022)) || return 1
[[ $current == / ]] && break
current=${current%/*}
[[ -n $current ]] || current=/
done
active_rules=$(/usr/bin/awk '!/^[[:space:]]*(#|$)/ { print }' "$BOOT_CLEANUP_FILE") || return 1
[[ $active_rules == 'r! /etc/sudoers.d/99-omarchy-nopasswd-*' ]]
}
prepare_state_file() {
local uid="$1" name="$2" expires="$3" timer="$4" tmp
tmp=$(/usr/bin/mktemp "$STATE_DIR/.state.XXXXXX") || return 1
if ! /usr/bin/printf 'UID=%s\nUSER=%s\nEXPIRES=%s\nTIMER=%s\n' \
"$uid" "$name" "$expires" "$timer" >"$tmp" ||
! /usr/bin/chown root:root "$tmp" || ! /usr/bin/chmod 0600 "$tmp"; then
/usr/bin/rm -f -- "$tmp"
return 1
fi
printf '%s' "$tmp"
}
start_expiry_timer() {
local uid="$1" expires="$2" timer="$3"
valid_uid "$uid" && valid_expiry "$expires" && valid_timer_for_uid "$uid" "$timer" || return 1
# Calendar timers use CLOCK_REALTIME and catch up immediately after resume;
# a monotonic OnActiveSec timer pauses while the machine is suspended.
/usr/bin/systemd-run --quiet --collect --on-calendar="@${expires}" \
--timer-property=AccuracySec=1s --unit="$timer" \
-- "$INSTALLED_SELF" __expire "$uid" || return 1
/usr/bin/systemctl is-active --quiet "${timer}.timer"
}
publish_rule() {
local uid="$1" name="$2" destination tmp
destination=$(rule_file "$uid")
tmp=$(/usr/bin/mktemp "$STATE_DIR/.sudoers.XXXXXX") || return 1
if ! /usr/bin/printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$name" >"$tmp" ||
! /usr/bin/chown root:root "$tmp" || ! /usr/bin/chmod 0440 "$tmp" ||
! /usr/sbin/visudo -cf "$tmp" >/dev/null ||
! /usr/bin/install -o root -g root -m 0440 -- "$tmp" "$destination"; then
/usr/bin/rm -f -- "$tmp"
return 1
fi
/usr/bin/rm -f -- "$tmp"
}
enable_locked() {
local uid="$1" minutes="$2" old_timer="" timer token expires pending_state now
resolve_account "$uid" || return 1
valid_minutes "$minutes" || return 1
prepare_root_state || return 1
verify_boot_cleanup || {
echo "omarchy-sudo-passwordless: package-owned boot cleanup rule is missing or unsafe" >&2
return 1
}
old_timer=$(read_state_timer "$uid" 2>/dev/null || true)
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid)
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
timer="omarchy-nopasswd-expire-${uid}-${token}"
now=$(current_epoch) || return 1
expires=$((10#$now + 10#$minutes * 60))
# State and a verified timer exist before the policy becomes reachable. If
# publication fails, cleanup removes both. During an update the old timer is
# deliberately kept until the replacement is active, so failure shortens the
# grant rather than extending it.
pending_state=$(prepare_state_file "$uid" "$ACCOUNT_NAME" "$expires" "$timer") || return 1
if ! start_expiry_timer "$uid" "$expires" "$timer"; then
/usr/bin/rm -f -- "$pending_state"
# Preserve the predecessor fix's fail-closed extension semantics: a caller
# must never mistake a failed replacement for a safely extended grant.
# The old timer is still armed here, but revoking the old rule as well is
# the unambiguous failure state.
cleanup_uid_locked "$uid" || true
return 1
fi
if ! /usr/bin/mv -fT -- "$pending_state" "$(state_file "$uid")"; then
stop_timer "$timer"
/usr/bin/rm -f -- "$pending_state"
cleanup_uid_locked "$uid" || true
return 1
fi
if ! publish_rule "$uid" "$ACCOUNT_NAME"; then
stop_timer "$timer"
/usr/bin/rm -f -- "$(state_file "$uid")" "$(rule_file "$uid")"
return 1
fi
now=$(current_epoch) || {
cleanup_uid_locked "$uid" || true
return 1
}
if ((10#$now >= 10#$expires)) || ! /usr/bin/systemctl is-active --quiet "${timer}.timer"; then
# The timer may have expired or failed between its initial verification and
# rule publication. Revoke synchronously so a suspended or heavily loaded
# machine cannot turn a short grant into a reboot-long one.
cleanup_uid_locked "$uid" || true
return 1
fi
[[ -z $old_timer || $old_timer == "$timer" ]] || stop_timer "$old_timer"
}
status_locked() {
local uid="$1" record state_name expires timer now remainder
resolve_account "$uid" || return 1
[[ -f $(rule_file "$uid") && ! -L $(rule_file "$uid") ]] || return 1
record=$(read_state_record "$uid") || {
cleanup_uid_locked "$uid"
return 1
}
state_name=${record%%$'\t'*}
remainder=${record#*$'\t'}
expires=${remainder%%$'\t'*}
timer=${record##*$'\t'}
[[ $state_name == "$ACCOUNT_NAME" ]] || {
cleanup_uid_locked "$uid"
return 1
}
now=$(current_epoch) || {
cleanup_uid_locked "$uid"
return 1
}
((10#$now < 10#$expires)) || {
cleanup_uid_locked "$uid"
return 1
}
/usr/bin/systemctl is-active --quiet "${timer}.timer" || {
cleanup_uid_locked "$uid"
return 1
}
}
root_dispatch() {
local action="$1"
shift
case "$action" in
__status)
(($# == 1)) && verify_sudo_caller "$1" || return 1
with_root_lock status_locked "$1"
;;
__enable)
(($# == 2)) && verify_sudo_caller "$1" && valid_minutes "$2" || return 1
with_root_lock enable_locked "$1" "$2"
;;
__disable)
(($# == 1)) && verify_sudo_caller "$1" || return 1
with_root_lock cleanup_uid_locked "$1"
;;
__expire)
(($# == 1)) && ((EUID == 0)) && valid_uid "$1" || return 1
with_root_lock cleanup_uid_locked "$1"
;;
__cleanup-all)
(($# == 0)) && ((EUID == 0)) || return 1
with_root_lock cleanup_all_locked
;;
*) return 1 ;;
esac
}
case "${1:-}" in
__status|__enable|__disable|__expire|__cleanup-all)
action=$1
shift
root_dispatch "$action" "$@"
exit
;;
esac
(($# <= 1)) || usage
minutes=${1:-$DEFAULT_MINUTES}
valid_minutes "$minutes" || usage
uid=$(/usr/bin/id -u)
valid_uid "$uid" || {
echo "omarchy-sudo-passwordless: cannot grant passwordless sudo to this account" >&2
exit 1
}
omarchy_security_sudo_supports_no_update || {
echo "This sudo does not support --no-update; refusing the passwordless-sudo workflow." >&2
exit 1
}
omarchy_security_install_sudo_cleanup_traps
/usr/bin/sudo -k >/dev/null 2>&1 || {
echo "Could not start from a cold sudo credential state." >&2
exit 1
}
echo "Toggle passwordless sudo..."
# Safety: if the file exists but the timer doesn't (e.g. after reboot), clean up
if sudo test -f "$NOPASSWD_FILE" && ! systemctl is-active "${TIMER_NAME}.timer" &>/dev/null; then
sudo rm "$NOPASSWD_FILE"
fi
# Check for the file directly — sudo -n can stay cached or be granted by other rules
if sudo test -f "$NOPASSWD_FILE"; then
if [[ $1 ]]; then
sudo systemctl stop "${TIMER_NAME}.timer" 2>/dev/null
arm_expiry || exit 1
echo "Passwordless sudo timer updated. It will now automatically disable in ${MINUTES} minutes."
else
sudo rm "$NOPASSWD_FILE"
sudo systemctl stop "${TIMER_NAME}.timer" 2>/dev/null
if /usr/bin/sudo -N -- "$INSTALLED_SELF" __status "$uid"; then
if (($# == 0)); then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __disable "$uid"
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
else
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
echo "Passwordless sudo timer updated. It will automatically disable in ${minutes} minutes."
fi
else
echo ""
echo "⚠️ WARNING: This will allow ANY process running as your user to"
echo "execute ANY command as root WITHOUT a password for ${MINUTES} minutes."
echo "execute ANY command as root WITHOUT a password for ${minutes} minutes."
echo ""
echo "This is useful for AI agents that need to run sudo commands,"
echo "but it significantly weakens the security of your system."
echo "Anyone or anything with access to your user account gets full root."
echo ""
echo "Passwordless sudo will automatically disable after ${MINUTES} minutes."
echo "Passwordless sudo will automatically disable after ${minutes} minutes,"
echo "including if the machine reboots before the timer fires."
echo "Run this command again to disable it early."
echo ""
if gum confirm "Enable passwordless sudo for ${MINUTES} minutes? This is a significant security risk!"; then
echo "${USER} ALL=(ALL) NOPASSWD: ALL" | sudo tee "$NOPASSWD_FILE" > /dev/null
sudo chmod 440 "$NOPASSWD_FILE"
arm_expiry || exit 1
if /usr/bin/gum confirm "Enable passwordless sudo for ${minutes} minutes? This is a significant security risk!"; then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
echo ""
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${MINUTES} minutes."
echo "A restart removes the passwordless sudo rule as well."
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${minutes} minutes."
else
echo "Aborted. No changes made."
fi