Skip to content

Capture.sh collapses eight distinct preflight exit codes to a constant 3 against a header (and an in-file comment) promising propagation #2089

Description

@Steffen025

Version

LifeOS 7.40.4 / Interceptor

What is broken

Capture.sh's header documents its exit codes as
0 ok; 2 bad args; 3 preflight failed (propagated); 7 target denied. Nothing is propagated. The
call site runs the gate inside if ! … and returns a constant — and the comment directly above it
states the contract a second time, in the same file, one line before the code that breaks it:

# --- 2. hardened preflight gate (propagate its exit + stderr verbatim) ---
if ! bash "$SCRIPT_DIR/PreflightIsolation.sh" >&2; then
    # Preflight already printed structured remediation. Do not try anyway.
    exit 3
fi

PreflightIsolation.sh distinguishes eight failure classes by exit code — 2 binary missing,
3 version-parse, 4 version too low, 5 nothing connected, 6 pinned context not connected, 7 target is
a denied profile, 8 test context unset, 9 no pinned Extension. All eight arrive at a Capture.sh
caller as 3. A caller cannot tell "you targeted a denied profile" (a refusal it must not retry)
from "re-pin the extension" (a fixable local state) from "the binary is missing".

Capture.sh also reuses 3 for an unrelated condition of its own — the pinned context missing from
the live connected set at :176 — so the code is overloaded inside the file as well as against the
header. The gate's stderr is passed through, so a human reading the terminal sees the real reason;
a script branching on the status code cannot.

The header's own words are the contract this contradicts: "structured remediation to stderr,
distinct non-zero exit per failure class."

Header, in-file comment and implementation disagree:

  • contract — LifeOS/install/skills/Interceptor/Tools/Capture.sh:21# Exit codes: 0 ok; 2 bad args; 3 preflight failed (propagated); 7 target denied
  • contract, second statement — LifeOS/install/skills/Interceptor/Tools/Capture.sh:130# --- 2. hardened preflight gate (propagate its exit + stderr verbatim) ---
  • writer — LifeOS/install/skills/Interceptor/Tools/Capture.sh:131-133 — the if ! bash …PreflightIsolation.sh; then … exit 3; fi block
  • second use of the same code — LifeOS/install/skills/Interceptor/Tools/Capture.sh:176
  • the codes being discarded — LifeOS/install/skills/Interceptor/Tools/PreflightIsolation.sh:36-39 (documented) and their exit sites throughout that file

Where (file:line)

LifeOS/install/skills/Interceptor/Tools/Capture.sh:133

Repro on a clean tree

# Three different preflight failure classes, driven through both scripts. A stub
# `interceptor` satisfies the binary and connection checks; HOME is redirected so
# no real preferences file is read.

git clone --branch v7.40.4 --depth 1 https://github.com/danielmiessler/LifeOS.git /tmp/lifeos-7404
cd /tmp/lifeos-7404 && git rev-parse HEAD
# → be9e8ef889f00a29f4fd677dee4772fdf32e07ce

mkdir -p /tmp/cap/bin /tmp/cap/home /tmp/cap/ext
printf '{"version":"1.0.0"}\n' > /tmp/cap/ext/manifest.json
cat > /tmp/cap/bin/interceptor <<'STUB'
#!/usr/bin/env bash
case "$1" in
  --version) echo "interceptor 0.16.9 (deadbee, 2026-08-14)" ;;
  contexts)  echo "[stub] → contexts"; echo "$STUB_CONTEXTS" ;;
  *) exit 1 ;;
esac
STUB
chmod +x /tmp/cap/bin/interceptor

D=/tmp/lifeos-7404/LifeOS/install/skills/Interceptor/Tools
probe() { # label  live-ctx  pinned-ctx  deny-list  ext-dir
  env -i HOME=/tmp/cap/home PATH=/tmp/cap/bin:/usr/bin:/bin STUB_CONTEXTS="$2" \
    INTERCEPTOR_TEST_CONTEXT_ID="$3" INTERCEPTOR_WORKING_PROFILE_IDS="$4" \
    INTERCEPTOR_EXT_DIR="$5" bash "$D/PreflightIsolation.sh" >/dev/null 2>&1; p=$?
  env -i HOME=/tmp/cap/home PATH=/tmp/cap/bin:/usr/bin:/bin STUB_CONTEXTS="$2" \
    INTERCEPTOR_TEST_CONTEXT_ID="$3" INTERCEPTOR_WORKING_PROFILE_IDS="$4" \
    INTERCEPTOR_EXT_DIR="$5" bash "$D/Capture.sh" "https://example.com" >/dev/null 2>&1; c=$?
  printf '%-32s preflight=%-2s  Capture.sh=%-2s\n' "$1" "$p" "$c"
}
probe "target denied"            'ctx-a-9' 'ctx-a-9' 'ctx-a-9' /tmp/cap/ext
probe "pinned ctx not connected" 'ctx-live' 'ctx-stale' ''     /tmp/cap/ext
probe "no Extension at all"      'ctx-a'   'ctx-a'   ''        /tmp/cap/nope
# → target denied                    preflight=7   Capture.sh=3
# → pinned ctx not connected         preflight=6   Capture.sh=3
# → no Extension at all              preflight=9   Capture.sh=3

Negative control

The left column of that same table is the control, and it is red without reference to any fix: the
three distinct codes exist and the gate emits them on the same three runs, so 3 is not the
only value available — it is a value chosen after a more specific one was produced and discarded.
The security-relevant class is the first row: a refusal the caller must never retry is delivered
under the same code as a re-pin-and-try-again condition.

Capture.sh propagates its own codes normally, so the collapse is specific to this call site and
not a limitation of the script:

env -i HOME=/tmp/cap/home PATH=/tmp/cap/bin:/usr/bin:/bin bash "$D/Capture.sh" >/dev/null 2>&1
echo "no-args EXIT=$?"
no-args EXIT=2

Suggested fix

Shape only, untested: capture the gate's status and re-exit with it —

bash "$SCRIPT_DIR/PreflightIsolation.sh" >&2 || exit $?

That makes the header's "(propagated)" true and costs one line. It does collide with Capture.sh's
own use of 3 at :176 and its own 7, so if you would rather keep Capture.sh's codes
independent, the alternative is an offset (say 20 + preflight_status) documented in the header.
Either way the header and the code should agree; today they do not, and the header is the part
callers read.

Before submitting

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions