From 0ad137c6dc1ad988a6b87105d71883e5c10966f4 Mon Sep 17 00:00:00 2001 From: Samuel Aubertin Date: Mon, 9 Mar 2026 19:06:36 +0100 Subject: [PATCH] Auto-enable trusted sloptrap capabilities and harden bash launcher --- .sloptrap | 2 +- Makefile | 2 +- README.md | 2 +- sloptrap | 84 ++++++++------- tests/run_tests.sh | 102 +++++++++++------- .../{wizzard_build => wizard_build}/.sloptrap | 3 +- .../{wizzard_empty => wizard_empty}/.sloptrap | 3 +- tests/wizard_existing/.sloptrap | 4 + tests/wizzard_existing/.sloptrap | 3 - 9 files changed, 118 insertions(+), 87 deletions(-) rename tests/{wizzard_build => wizard_build}/.sloptrap (56%) rename tests/{wizzard_empty => wizard_empty}/.sloptrap (56%) create mode 100644 tests/wizard_existing/.sloptrap delete mode 100644 tests/wizzard_existing/.sloptrap diff --git a/.sloptrap b/.sloptrap index 2ca19ec..0900dce 100644 --- a/.sloptrap +++ b/.sloptrap @@ -1,4 +1,4 @@ name=skz-sloptrap packages_extra=make shellcheck jq podman -capabilities= +capabilities=apt-install nested-podman packet-capture allow_host_network=false diff --git a/Makefile b/Makefile index 04fbe46..8c8a5ba 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ install update: $(PROGRAM) @printf '%b%bSuccess!%b Run it with:%b\n' '$(PREFIX_COMMENT)' '$(COLOR_HIGHLIGHT)' '$(COLOR_TEXT)' '$(RESET)' @printf '%b%b%b %s /path/to/project%b\n' '$(PREFIX_HIGHLIGHT)' '$(COLOR_HIGHLIGHT)' '\033[1m' '$(PROGRAM)' '$(RESET)' @printf '%b%bConfigure your project with the wizard:%b\n' '$(PREFIX_TEXT)' '$(COLOR_TEXT)' '$(RESET)' - @printf '%b%b sloptrap /path/to/project wizzard%b\n' '$(PREFIX_COMMENT)' '$(COLOR_COMMENT)' '$(RESET)' + @printf '%b%b sloptrap /path/to/project wizard%b\n' '$(PREFIX_COMMENT)' '$(COLOR_COMMENT)' '$(RESET)' uninstall: @printf '%b%bRemoving%b %b%s%b\n' '$(PREFIX_COMMENT)' '$(COLOR_TEXT)' '$(COLOR_TEXT)' '$(COLOR_COMMENT)' '$(INSTALL_PATH)' '$(RESET)' diff --git a/README.md b/README.md index 7020ef5..799ff52 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Targets are supplied after the code directory. When omitted, sloptrap defaults t | `resume ` | Continues a Codex session by running `codex resume ` inside the container (builds if needed). | | `login` | Starts Codex in login mode to bootstrap shared `${HOME}/.codex/auth.json` credentials. | | `shell` | Launches `/bin/bash` inside the container for debugging. | -| `wizzard` | Creates or updates `.sloptrap` interactively (no build); rerun `build` or `rebuild` afterward. | +| `wizard` | Creates or updates `.sloptrap` interactively (no build); rerun `build` or `rebuild` afterward. | | `stop` | Best-effort stop of the running container (if any). | | `clean` | Removes `.sloptrap-ignores`, deletes the container/image, and stops the container if necessary. | diff --git a/sloptrap b/sloptrap index 2acf70d..5783e3b 100755 --- a/sloptrap +++ b/sloptrap @@ -1,5 +1,8 @@ #!/usr/bin/env bash # sloptrap +if [ -z "${BASH_VERSION:-}" ]; then + exec bash "$0" "$@" +fi set -euo pipefail IS_MAC=false @@ -166,7 +169,6 @@ usage() { info_line "Options:\n" comment_line " --dry-run Show planned container command(s) and exit\n" comment_line " --print-config Display resolved manifest values\n" - comment_line " --enable-capability Enable a trusted runtime capability for this run\n" comment_line " --trust-capabilities Trust the manifest's requested capabilities for this build\n" comment_line " -h, --help Show this message\n" info_line "\n" @@ -180,7 +182,7 @@ usage() { comment_line " run Build if needed, then launch Codex\n" comment_line " resume Build if needed, then run 'codex resume '\n" comment_line " shell Drop into an interactive /bin/bash session\n" - comment_line " wizzard Create or update %s interactively\n" "$MANIFEST_BASENAME" + comment_line " wizard Create or update %s interactively\n" "$MANIFEST_BASENAME" comment_line " clean Remove the project container/image cache\n" comment_line " prune Remove dangling/unused sloptrap images\n" } @@ -226,7 +228,6 @@ declare -a SLOPTRAP_IGNORE_ENTRIES=() declare -a IGNORE_MOUNT_ARGS=() declare -a CODEX_ARGS_ARRAY=() declare -a DEFAULT_TARGETS=() -declare -a ENABLED_CAPABILITIES_ARGS=() MANIFEST_PRESENT=false CURRENT_IGNORE_FILE="" @@ -801,17 +802,6 @@ capability_list_contains() { return 1 } -validate_enabled_capabilities() { - local enabled=$1 - local requested=$2 - local capability - for capability in $enabled; do - if ! capability_list_contains "$requested" "$capability"; then - error "runtime capability '$capability' is not requested by $MANIFEST_PATH" - fi - done -} - detect_container_engine() { local override=${SLOPTRAP_CONTAINER_ENGINE-} if [[ -n $override ]]; then @@ -878,12 +868,12 @@ prompt_manifest_value() { printf '%s [%s]: ' "$label" "$default_value" >"$tty_path" printf '%b' "$RESET" >"$tty_path" if ! IFS= read -r input <"$tty_path"; then - error "wizzard requires an interactive terminal" + error "wizard requires an interactive terminal" fi printf '%s' "$input" } -validate_wizzard_name() { +validate_wizard_name() { local value=$1 [[ -n $value ]] || error "$MANIFEST_PATH: name must not be empty" if [[ ! $value =~ $VALID_NAME_REGEX ]]; then @@ -891,7 +881,7 @@ validate_wizzard_name() { fi } -normalize_wizzard_allow_host_network() { +normalize_wizard_allow_host_network() { local value=${1,,} case "$value" in 1|true|yes) printf 'true' ;; @@ -900,13 +890,13 @@ normalize_wizzard_allow_host_network() { esac } -run_wizzard() { +run_wizard() { local manifest_path=$1 if [[ -L $manifest_path ]]; then error "$manifest_path: manifest must not be a symlink" fi if [[ ! -t 0 ]]; then - error "wizzard requires an interactive terminal" + error "wizard requires an interactive terminal" fi if [[ ! -f $manifest_path ]]; then print_banner @@ -934,7 +924,7 @@ run_wizzard() { value=$(prompt_manifest_value "name" "$default_name") value=$(trim "$value") [[ -n $value ]] || value=$default_name - validate_wizzard_name "$value" + validate_wizard_name "$value" default_name=$value break done @@ -951,12 +941,25 @@ run_wizzard() { break done + while true; do + info_line "capabilities: Optional privileged features (%s).\n" "${SLOPTRAP_SUPPORTED_CAPABILITIES[*]}" + value=$(prompt_manifest_value "capabilities" "$default_capabilities") + value=$(trim "$value") + [[ -n $value ]] || value=$default_capabilities + value=$(normalize_capability_list "$value") + if [[ -n $value ]]; then + validate_capability_list "capabilities" "$value" "$manifest_path" + fi + default_capabilities=$value + break + done + while true; do info_line "allow_host_network: Use host networking instead of an isolated bridge.\n" value=$(prompt_manifest_value "allow_host_network" "$default_allow_host_network") value=$(trim "$value") [[ -n $value ]] || value=$default_allow_host_network - default_allow_host_network=$(normalize_wizzard_allow_host_network "$value") + default_allow_host_network=$(normalize_wizard_allow_host_network "$value") break done @@ -1028,12 +1031,16 @@ print_manifest_summary() { } build_runtime_context_prompt() { - local manifest_present prompt requested enabled network_mode + local manifest_present prompt manifest_capabilities trusted enabled network_mode manifest_present="false" if [[ -f $MANIFEST_PATH ]]; then manifest_present="true" fi - requested=${REQUESTED_CAPABILITIES:-none} + manifest_capabilities=${REQUESTED_CAPABILITIES:-none} + trusted="none" + if [[ -n $REQUESTED_CAPABILITIES ]] && capability_trust_matches_current; then + trusted=$REQUESTED_CAPABILITIES + fi enabled=${ENABLED_CAPABILITIES:-none} network_mode="isolated" if [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then @@ -1058,7 +1065,8 @@ Current resolved sloptrap state: - manifest_present=$manifest_present - project_name=$PROJECT_NAME - packages_extra=${PACKAGES_EXTRA:-none} -- requested_capabilities=$requested +- manifest_capabilities=$manifest_capabilities +- trusted_capabilities=$trusted - enabled_capabilities=$enabled - network_mode=$network_mode - runtime_flags=$CODEX_ARGS_DISPLAY @@ -1755,8 +1763,8 @@ dispatch_target() { build_if_missing run_shell_target ;; - wizzard) - run_wizzard "$MANIFEST_PATH" + wizard) + run_wizard "$MANIFEST_PATH" exit 0 ;; stop) @@ -1789,12 +1797,6 @@ while [[ $# -gt 0 ]]; do PRINT_CONFIG=true shift ;; - --enable-capability) - shift - [[ $# -gt 0 ]] || error "--enable-capability requires a capability name" - ENABLED_CAPABILITIES_ARGS+=("$1") - shift - ;; --trust-capabilities) TRUST_CAPABILITIES=true shift @@ -1844,11 +1846,11 @@ fi if [[ ${#TARGETS_INPUT[@]} -gt 0 ]]; then target_index=0 while (( target_index < ${#TARGETS_INPUT[@]} )); do - if [[ ${TARGETS_INPUT[$target_index]} == "wizzard" ]]; then + if [[ ${TARGETS_INPUT[$target_index]} == "wizard" ]]; then if (( ${#TARGETS_INPUT[@]} > 1 )); then - warn "wizzard runs standalone; ignoring other targets" + warn "wizard runs standalone; ignoring other targets" fi - run_wizzard "$MANIFEST_PATH" + run_wizard "$MANIFEST_PATH" exit 0 fi ((target_index+=1)) @@ -1858,13 +1860,13 @@ fi if [[ ! -f $MANIFEST_PATH ]]; then if targets_need_build "${TARGETS_INPUT[@]}"; then if [[ -t 0 ]]; then - run_wizzard "$MANIFEST_PATH" + run_wizard "$MANIFEST_PATH" SKIP_BUILD_BANNER=true MANIFEST=() MANIFEST_PRESENT=true parse_manifest "$MANIFEST_PATH" else - warn "missing $MANIFEST_BASENAME; proceeding with defaults (run '$0 $CODE_DIR wizzard' to create one)" + warn "missing $MANIFEST_BASENAME; proceeding with defaults (run '$0 $CODE_DIR wizard' to create one)" fi fi fi @@ -1902,9 +1904,11 @@ if [[ -n $REQUESTED_CAPABILITIES ]]; then ensure_safe_for_make "capabilities" "$REQUESTED_CAPABILITIES" fi validate_capability_list "capabilities" "$REQUESTED_CAPABILITIES" -ENABLED_CAPABILITIES=$(normalize_capability_list "${ENABLED_CAPABILITIES_ARGS[*]-}") -validate_capability_list "--enable-capability" "$ENABLED_CAPABILITIES" "command line" -validate_enabled_capabilities "$ENABLED_CAPABILITIES" "$REQUESTED_CAPABILITIES" +if [[ -n $REQUESTED_CAPABILITIES ]] && { $TRUST_CAPABILITIES || capability_trust_matches_current; }; then + ENABLED_CAPABILITIES="$REQUESTED_CAPABILITIES" +else + ENABLED_CAPABILITIES="" +fi if [[ -n ${MANIFEST[allow_host_network]-} ]]; then case "${MANIFEST[allow_host_network],,}" in 1|true|yes) diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 3130912..ee6a710 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -335,7 +335,10 @@ run_runtime_context_prompt() { if [[ -z $run_line || $run_line != *"You are running inside sloptrap"* ]]; then record_failure "runtime_context_prompt: startup prompt missing from fresh run" fi - if ! grep -q -- "manifest_present=true" "$STUB_LOG" || ! grep -q -- "requested_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG"; then + if ! grep -q -- "manifest_present=true" "$STUB_LOG" \ + || ! grep -q -- "manifest_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG" \ + || ! grep -q -- "trusted_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG" \ + || ! grep -q -- "enabled_capabilities=apt-install nested-podman packet-capture" "$STUB_LOG"; then record_failure "runtime_context_prompt: runtime summary missing manifest or capability state" fi if [[ -n $login_line && $login_line == *"You are running inside sloptrap"* ]]; then @@ -344,6 +347,22 @@ run_runtime_context_prompt() { teardown_stub_env } +run_sh_reexec() { + local scenario_dir="$TEST_ROOT/capability_repo" + printf '==> sh_reexec\n' + setup_stub_env + if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ + sh "$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" /dev/null 2>&1; then + record_failure "sh_reexec: sloptrap exited non-zero when launched via sh" + teardown_stub_env + return + fi + if ! grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then + record_failure "sh_reexec: startup prompt missing after sh re-exec" + fi + teardown_stub_env +} + run_resume_omits_runtime_context() { local scenario_dir="$TEST_ROOT/capability_repo" local session_id="019a81b7-32d2-7622-8639-6698c6579625" @@ -569,77 +588,83 @@ run_invalid_allow_host_network() { fi } -run_wizzard_create_manifest() { - local scenario_dir="$TEST_ROOT/wizzard_empty" - printf '==> wizzard_create_manifest\n' +run_wizard_create_manifest() { + local scenario_dir="$TEST_ROOT/wizard_empty" + printf '==> wizard_create_manifest\n' if ! can_run_script_pty; then - printf 'skipping wizzard_create_manifest: script PTY support not available\n' + printf 'skipping wizard_create_manifest: script PTY support not available\n' return fi rm -f "$scenario_dir/.sloptrap" - local input=$'\n\n\n\n\n' - if ! printf '%s' "$input" | script -q -c "$SLOPTRAP_BIN \"$scenario_dir\" wizzard" /dev/null >/dev/null 2>&1; then - record_failure "wizzard_create_manifest: wizzard failed" + local input=$'\n\n\n\n' + if ! printf '%s' "$input" | script -q -c "$SLOPTRAP_BIN \"$scenario_dir\" wizard" /dev/null >/dev/null 2>&1; then + record_failure "wizard_create_manifest: wizard failed" return fi if [[ ! -f $scenario_dir/.sloptrap ]]; then - record_failure "wizzard_create_manifest: manifest not created" + record_failure "wizard_create_manifest: manifest not created" return fi - if ! grep -qx "name=wizzard_empty" "$scenario_dir/.sloptrap"; then - record_failure "wizzard_create_manifest: name default mismatch" + if ! grep -qx "name=wizard_empty" "$scenario_dir/.sloptrap"; then + record_failure "wizard_create_manifest: name default mismatch" fi if ! grep -qx "packages_extra=" "$scenario_dir/.sloptrap"; then - record_failure "wizzard_create_manifest: packages_extra mismatch" + record_failure "wizard_create_manifest: packages_extra mismatch" + fi + if ! grep -qx "capabilities=" "$scenario_dir/.sloptrap"; then + record_failure "wizard_create_manifest: capabilities mismatch" fi if ! grep -qx "allow_host_network=false" "$scenario_dir/.sloptrap"; then - record_failure "wizzard_create_manifest: allow_host_network mismatch" + record_failure "wizard_create_manifest: allow_host_network mismatch" fi } -run_wizzard_existing_defaults() { - local scenario_dir="$TEST_ROOT/wizzard_existing" - printf '==> wizzard_existing_defaults\n' +run_wizard_existing_defaults() { + local scenario_dir="$TEST_ROOT/wizard_existing" + printf '==> wizard_existing_defaults\n' if ! can_run_script_pty; then - printf 'skipping wizzard_existing_defaults: script PTY support not available\n' + printf 'skipping wizard_existing_defaults: script PTY support not available\n' return fi - local input=$'\n\n\n\n\n' - if ! printf '%s' "$input" | script -q -c "$SLOPTRAP_BIN \"$scenario_dir\" wizzard" /dev/null >/dev/null 2>&1; then - record_failure "wizzard_existing_defaults: wizzard failed" + local input=$'\n\n\n\n' + if ! printf '%s' "$input" | script -q -c "$SLOPTRAP_BIN \"$scenario_dir\" wizard" /dev/null >/dev/null 2>&1; then + record_failure "wizard_existing_defaults: wizard failed" return fi - if ! grep -qx "name=custom-wizzard" "$scenario_dir/.sloptrap"; then - record_failure "wizzard_existing_defaults: name not preserved" + if ! grep -qx "name=custom-wizard" "$scenario_dir/.sloptrap"; then + record_failure "wizard_existing_defaults: name not preserved" fi if ! grep -qx "packages_extra=make git" "$scenario_dir/.sloptrap"; then - record_failure "wizzard_existing_defaults: packages_extra not preserved" + record_failure "wizard_existing_defaults: packages_extra not preserved" + fi + if ! grep -qx "capabilities=apt-install packet-capture" "$scenario_dir/.sloptrap"; then + record_failure "wizard_existing_defaults: capabilities not preserved" fi if ! grep -qx "allow_host_network=true" "$scenario_dir/.sloptrap"; then - record_failure "wizzard_existing_defaults: allow_host_network not preserved" + record_failure "wizard_existing_defaults: allow_host_network not preserved" fi } -run_wizzard_build_trigger() { - local scenario_dir="$TEST_ROOT/wizzard_build" - printf '==> wizzard_build_trigger\n' +run_wizard_build_trigger() { + local scenario_dir="$TEST_ROOT/wizard_build" + printf '==> wizard_build_trigger\n' if ! can_run_script_pty; then - printf 'skipping wizzard_build_trigger: script PTY support not available\n' + printf 'skipping wizard_build_trigger: script PTY support not available\n' return fi setup_stub_env rm -f "$scenario_dir/.sloptrap" - local input=$'\n\n\n\n\n' + local input=$'\n\n\n\n' if ! printf '%s' "$input" | script -q -c "env PATH=\"$STUB_BIN:$PATH\" HOME=\"$STUB_HOME\" FAKE_PODMAN_LOG=\"$STUB_LOG\" FAKE_PODMAN_INSPECT_FAIL=1 \"$SLOPTRAP_BIN\" \"$scenario_dir\"" /dev/null >/dev/null 2>&1; then - record_failure "wizzard_build_trigger: sloptrap failed" + record_failure "wizard_build_trigger: sloptrap failed" teardown_stub_env return fi if [[ ! -f $scenario_dir/.sloptrap ]]; then - record_failure "wizzard_build_trigger: manifest not created" + record_failure "wizard_build_trigger: manifest not created" fi if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then - record_failure "wizzard_build_trigger: build not invoked after wizard" + record_failure "wizard_build_trigger: build not invoked after wizard" fi teardown_stub_env } @@ -649,7 +674,7 @@ run_capability_trust_required() { printf '==> capability_trust_required\n' setup_stub_env if PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$SLOPTRAP_BIN" --enable-capability apt-install "$scenario_dir" /dev/null 2>&1; then + "$SLOPTRAP_BIN" "$scenario_dir" /dev/null 2>&1; then record_failure "capability_trust_required: expected failure without trusted capabilities" fi teardown_stub_env @@ -660,9 +685,7 @@ run_capability_profiles() { printf '==> capability_profiles\n' setup_stub_env if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \ - "$SLOPTRAP_BIN" --trust-capabilities --enable-capability apt-install \ - --enable-capability packet-capture --enable-capability nested-podman \ - "$scenario_dir" /dev/null 2>&1; then + "$SLOPTRAP_BIN" --trust-capabilities "$scenario_dir" /dev/null 2>&1; then record_failure "capability_profiles: sloptrap exited non-zero" teardown_stub_env return @@ -703,6 +726,7 @@ run_helper_symlink run_secret_mask run_resume_target run_runtime_context_prompt +run_sh_reexec run_resume_omits_runtime_context run_auth_file_mount run_project_state_isolation @@ -718,9 +742,9 @@ run_invalid_manifest_sandbox run_invalid_manifest_packages run_invalid_manifest_capabilities run_invalid_allow_host_network -run_wizzard_create_manifest -run_wizzard_existing_defaults -run_wizzard_build_trigger +run_wizard_create_manifest +run_wizard_existing_defaults +run_wizard_build_trigger run_capability_trust_required run_capability_profiles diff --git a/tests/wizzard_build/.sloptrap b/tests/wizard_build/.sloptrap similarity index 56% rename from tests/wizzard_build/.sloptrap rename to tests/wizard_build/.sloptrap index 5252fde..55fd1e6 100644 --- a/tests/wizzard_build/.sloptrap +++ b/tests/wizard_build/.sloptrap @@ -1,3 +1,4 @@ -name=wizzard_build +name=wizard_build packages_extra= +capabilities= allow_host_network=false diff --git a/tests/wizzard_empty/.sloptrap b/tests/wizard_empty/.sloptrap similarity index 56% rename from tests/wizzard_empty/.sloptrap rename to tests/wizard_empty/.sloptrap index 12bbebe..47c7920 100644 --- a/tests/wizzard_empty/.sloptrap +++ b/tests/wizard_empty/.sloptrap @@ -1,3 +1,4 @@ -name=wizzard_empty +name=wizard_empty packages_extra= +capabilities= allow_host_network=false diff --git a/tests/wizard_existing/.sloptrap b/tests/wizard_existing/.sloptrap new file mode 100644 index 0000000..1cb5ede --- /dev/null +++ b/tests/wizard_existing/.sloptrap @@ -0,0 +1,4 @@ +name=custom-wizard +packages_extra=make git +capabilities=apt-install packet-capture +allow_host_network=true diff --git a/tests/wizzard_existing/.sloptrap b/tests/wizzard_existing/.sloptrap deleted file mode 100644 index b323158..0000000 --- a/tests/wizzard_existing/.sloptrap +++ /dev/null @@ -1,3 +0,0 @@ -name=custom-wizzard -packages_extra=make git -allow_host_network=true