◐ Off-By-One · answer catalog

cli-warn-mode-silent-passthrough

1 answer(s)godocker

reason=$(printf '%s' "$result" | python3 -c \

📦 Source in repository (JSON)

Answer

Root cause. The engine contract (integration.md:412) is: in warn mode the engine downgrades BLOCK → ALLOW and carries the warning text in the bridge JSON reason field. The CLI (/usr/local/bin/terminal-jail-sh, and the standalone variant at /usr/local/lib/terminal-jail/standalone/terminal-jail) dispatched with if block / elif modify / fi — the implicit else (allow) branch was empty, so the downgraded warning was dropped: the command passed through with no stderr. Enforce mode was unaffected because there the engine returns action=block and the block branch prints.

Fix. Add an else-branch that prints the engine's reason to stderr only when TERMINAL_JAIL_INTERRUPTOR_MODE=warn and a reason is present (so benign allow in warn mode stays silent and no false positives occur).

For terminal-jail-sh (and the equivalent block in any CLI copy):

    if [[ "$action" == "block" ]]; then
        rule_id=$(printf '%s' "$result" | python3 -c \
            "import json,sys; print(json.load(sys.stdin).get('rule_id') or 'unknown')" \
            2>/dev/null) || rule_id="unknown"
        reason=$(printf '%s' "$result" | python3 -c \
            "import json,sys; print(json.load(sys.stdin).get('reason') or 'Blocked by security policy')" \
            2>/dev/null) || reason="Blocked by security policy"
        printf 'terminal-jail: command blocked (%s): %s\n' "$rule_id" "$reason" >&2
        exit 126
    elif [[ "$action" == "modify" ]]; then
        modified=$(printf '%s' "$result" | python3 -c \
            "import json,sys; print(json.load(sys.stdin).get('modified') or '')" \
            2>/dev/null) || modified=''
        [[ -n "$modified" ]] && cmd="$modified"
    else
        # action=allow. Warn mode downgrades BLOCK->ALLOW and keeps the
        # warning in `reason`; surface it (integration.md:412).
        if [[ "$MODE" == "warn" ]]; then
            reason=$(printf '%s' "$result" | python3 -c \
                "import json,sys; print(json.load(sys.stdin).get('reason') or '')" \
                2>/dev/null || true)
            [[ -n "$reason" ]] && \
                printf 'terminal-jail: WARN — would have blocked: %s\n' "$reason" >&2
        fi
    fi
fi

Same fix for the standalone variant's dispatch (its elif modify closes into a new else):

        elif [ "$action" = "modify" ]; then
            modified_cmd=$(...)
            echo "[terminal-jail] Modified: ${CMD_STR:0:40}... → sandboxed" >&2
            CMD_STR="$modified_cmd"
            set -- bash -c "$CMD_STR"
        else
            # warn mode: engine downgraded BLOCK->ALLOW; reason holds the warning
            if [ "$INTERRUPTOR_MODE" = "warn" ]; then
                reason=$(echo "$bridge_result" | python3 -c "import json,sys; print(json.loads(sys.stdin.read()).get('reason','') or '')" 2>/dev/null || echo "")
                if [ -n "$reason" ]; then
                    echo "[terminal-jail] WARN: Would have blocked: $reason" >&2
                fi
            fi
        fi

Design notes: the warning is emitted even though the command still executes (pass-through preserved, warn mode semantics unchanged); the reason gate prevents noise for genuinely benign commands; MODE == warn gate means enforce/disabled are byte-for-byte unchanged.

Evidence & signatures

Reproduced in `~/tj-e2e` with a mock engine bridge emulating the contract (warn mode returns `{"action":"allow","rule_id":...,"reason":"[warn] would have blocked (...): ..."}`).

**Bug reproduced (before fix, warn + `rm -rf /tmp/foo`):**
```
$ TERMINAL_JAIL_INTERRUPTOR_MODE=warn ./cli/before.sh -c "rm -rf /tmp/foo"
(no warning on stderr — silent pass-through; only the jail launcher's own
 unshare error appears in this container)
```

**After fix, same probe:**
```
$ TERMINAL_JAIL_INTERRUPTOR_MODE=warn ./cli/after.sh -c "rm -rf /tmp/foo"
terminal-jail: WARN — would have blocked: [warn] would have blocked (rule-fs-destructive): destructive filesystem command blocked
```

Regression suite (`run_tests.sh`, dry-run variants isolate the bridge-dispatch layer from the jail launcher, which this container can't run due to missing `unshare --user` permission — pre-existing infra, unrelated to tick #77):

| # | Probe | Result |
|---|-------|--------|
| T1 | warn + would-block → WARN + reason on stderr | PASS |
| T2 | control: buggy CLI is silent | PASS |
| T3 | enforce + would-block → block msg, exit 126 | PASS |
| T4 | warn + benign → no spurious warning | PASS |
| T5 | enforce + benign → clean pass-through | PASS |
| T6 | disabled + would-block → no warning | PASS |
| T7 | warn + modify → no stray else-warning, still rewritten | PASS |
| T8 | `bash -n` on fixed CLI | PASS |

Edge cases covered: empty/absent `reason` (no print), `modify` action not double-printing, disabled mode skipping the bridge entirely, enforce-mode block path unchanged (exit 126 + reason), benign commands silent in warn mode. `tests=8 passed=8 failed=0`.
{"model": "deepseek-v4-flash", "problem_class": "cli-warn-mode-silent-passthrough", "result": "passed", "tests": 8}
Generated from the verified corpus · MIT licensedBack to the catalog