Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## v1.0.1 — 2026-04-13

### Security
- Fix command injection via `--resume` argument in `brif` launcher (SESSION_ID
sanitized; passed to tmux via `set-environment` instead of inline shell string)
- Fix directory traversal in `brif-pane.sh` and `brif-pane.ps1`: SESSION_ID
now validated against `^[a-zA-Z0-9._-]+$` before use in file paths
- Upgrade ip-api.com geolocation fetch from HTTP to HTTPS in `statusline.sh`
and `statusline.ps1`
- Replace `printf '%b'` with `printf '%s'` in `statusline.sh` (ANSI constants
converted to `$'\033'`) — prevents terminal-escape injection from
user-controlled strings such as directory names and git branch names
- Add `chmod 700` on all brif session directories (`install.sh`, `brif`,
`hooks/post-tool-use.sh`, `hooks/user-prompt.sh`) to protect `events.jsonl`
(which logs full user prompt text) on multi-user systems
- Write initial `mission.json` atomically via `.tmp` + `mv` in `brif` launcher

## v1.0.0 — 2026-04-03

### Features
Expand Down
18 changes: 12 additions & 6 deletions brif
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,27 @@ SESSION_ID="brif-$(date +%s | (shasum 2>/dev/null || md5sum) | head -c 8)"
prev_arg=""
for arg in "$@"; do
if [[ "$prev_arg" == "--resume" ]]; then
SESSION_ID="brif-${arg:0:8}"
# Sanitize: strip any character outside the allowed set before truncating
clean_arg="${arg//[^a-zA-Z0-9._-]/}"
SESSION_ID="brif-${clean_arg:0:8}"
fi
prev_arg="$arg"
done

# --- Create session directory ---
SESSION_DIR="$BRIF_DIR/$SESSION_ID"
mkdir -p "$SESSION_DIR"
chmod 700 "$SESSION_DIR"

# --- Create/update current symlink ---
ln -sfn "$SESSION_DIR" "$BRIF_DIR/current"

# --- Generate color if needed ---
if [[ ! -f "$SESSION_DIR/mission.json" ]]; then
color=$(printf '#%06x' $(( $(od -An -N3 -tu4 /dev/urandom | tr -d ' ') % 16777216 )))
echo "{\"version\":1,\"goal\":\"\",\"progress\":[],\"remaining\":[],\"status\":\"active\",\"pending\":\"\",\"color\":\"$color\"}" \
> "$SESSION_DIR/mission.json"
printf '{"version":1,"goal":"","progress":[],"remaining":[],"status":"active","pending":"","color":"%s"}\n' "$color" \
> "$SESSION_DIR/mission.json.tmp"
mv "$SESSION_DIR/mission.json.tmp" "$SESSION_DIR/mission.json"
fi

# --- Export session ID for hooks ---
Expand All @@ -68,10 +72,12 @@ TMUX_SESSION="brif-$$"
tmux new-session -d -s "$TMUX_SESSION" -x "$(tput cols)" -y "$(tput lines)"

# Top pane: brif renderer (5 lines)
tmux split-window -t "$TMUX_SESSION" -v -l 7 -b "BRIF_SESSION_ID=$SESSION_ID bash '$PANE_SCRIPT' '$SESSION_ID'"
# Pass SESSION_ID via tmux set-environment to avoid shell-quoting injection
tmux set-environment -t "$TMUX_SESSION" BRIF_SESSION_ID "$SESSION_ID"
tmux split-window -t "$TMUX_SESSION" -v -l 7 -b "bash '$PANE_SCRIPT' '$SESSION_ID'"

# Bottom pane: Claude Code (quote args safely)
tmux send-keys -t "$TMUX_SESSION.1" "BRIF_SESSION_ID='$SESSION_ID' claude $(printf '%q ' "$@")" Enter
# Bottom pane: Claude Code (quote args safely; SESSION_ID already in tmux env)
tmux send-keys -t "$TMUX_SESSION.1" "claude $(printf '%q ' "$@")" Enter

# Attach — when Claude exits, the bottom pane closes and tmux auto-destroys
# the session (set destroy-unattached). If user detaches, session stays alive.
Expand Down
4 changes: 4 additions & 0 deletions brif-pane.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ $DIM = "${ESC}[2m"

# --- File paths ---
if (-not $SessionId) { $SessionId = $args[0] }
if ($SessionId -and $SessionId -notmatch '^[a-zA-Z0-9._-]+$') {
Write-Error "brif-pane: invalid session ID"
exit 1
}
$MISSION_FILE = if ($env:BRIF_MISSION_FILE) { $env:BRIF_MISSION_FILE } else { Join-Path $HOME ".claude\brif\$SessionId\mission.json" }
$METRICS_FILE = if ($env:BRIF_METRICS_FILE) { $env:BRIF_METRICS_FILE } else { Join-Path $HOME ".claude\brif\$SessionId\metrics.json" }
$EVENTS_FILE = if ($env:BRIF_EVENTS_FILE) { $env:BRIF_EVENTS_FILE } else { Join-Path $HOME ".claude\brif\$SessionId\events.jsonl" }
Expand Down
4 changes: 4 additions & 0 deletions brif-pane.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ DIM="${ESC}[2m"

# --- File paths ---
SESSION_ID="${1:-}"
if [[ -n "$SESSION_ID" && ! "$SESSION_ID" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "brif-pane: invalid session ID" >&2
exit 1
fi
MISSION_FILE="${BRIF_MISSION_FILE:-$HOME/.claude/brif/$SESSION_ID/mission.json}"
METRICS_FILE="${BRIF_METRICS_FILE:-$HOME/.claude/brif/$SESSION_ID/metrics.json}"
EVENTS_FILE="${BRIF_EVENTS_FILE:-$HOME/.claude/brif/$SESSION_ID/events.jsonl}"
Expand Down
1 change: 1 addition & 0 deletions hooks/post-tool-use.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fi

session_dir="$HOME/.claude/brif/$session_id"
mkdir -p "$session_dir"
chmod 700 "$session_dir"

input_json="$(cat)"

Expand Down
1 change: 1 addition & 0 deletions hooks/user-prompt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fi

session_dir="$HOME/.claude/brif/$session_id"
mkdir -p "$session_dir"
chmod 700 "$session_dir"

input_json="$(cat)"

Expand Down
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ success "Downloaded statusline.sh to $SCRIPT_DEST (executable)"
# Step 2b: Download brif files
info "Downloading brif files ..."
mkdir -p "$BRIF_DIR" "$BRIF_HOOKS_DIR"
chmod 700 "$BRIF_DIR" "$BRIF_HOOKS_DIR"

for entry in "${BRIF_FILES[@]}"; do
src="${entry%%:*}"
Expand Down
2 changes: 1 addition & 1 deletion statusline.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ if ($CFG_SHOW_WEATHER) {
# wttr.in: &m = metric (Celsius), &u = USCS (Fahrenheit)
$unitParam = if ($CFG_WEATHER_UNIT -eq "F") { "&u" } else { "&m" }
$wResp = Invoke-RestMethod -Uri "https://wttr.in/?format=%c|%t${unitParam}" -TimeoutSec 3 -ErrorAction Stop
$gResp = Invoke-RestMethod -Uri "http://ip-api.com/json/?fields=countryCode,city" -TimeoutSec 3 -ErrorAction Stop
$gResp = Invoke-RestMethod -Uri "https://ip-api.com/json/?fields=countryCode,city" -TimeoutSec 3 -ErrorAction Stop
$cc = if ($gResp.countryCode) { $gResp.countryCode } else { "" }
$city = if ($gResp.city) { $gResp.city } else { "" }
$wResp = $wResp.Trim()
Expand Down
32 changes: 16 additions & 16 deletions statusline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ CFG_STYLE="banner" # "banner" (v2) or "classic" (v1 look)
# ---------------------------------------------------------------------------
# ANSI color codes
# ---------------------------------------------------------------------------
C_RESET='\033[0m'
C_BOLD='\033[1m'
C_CYAN='\033[36m'
C_MAGENTA='\033[35m'
C_GREEN='\033[32m'
C_YELLOW='\033[33m'
C_RED='\033[31m'
C_DIM='\033[2m'
C_RESET=$'\033[0m'
C_BOLD=$'\033[1m'
C_CYAN=$'\033[36m'
C_MAGENTA=$'\033[35m'
C_GREEN=$'\033[32m'
C_YELLOW=$'\033[33m'
C_RED=$'\033[31m'
C_DIM=$'\033[2m'

# ---------------------------------------------------------------------------
# Read JSON from stdin
Expand Down Expand Up @@ -213,7 +213,7 @@ get_weather_info() {

# Fetch country code from ip-api.com
local country
country="$(curl -s --max-time 3 'http://ip-api.com/json/?fields=countryCode' 2>/dev/null | jq -r '.countryCode // ""' 2>/dev/null)"
country="$(curl -s --max-time 3 'https://ip-api.com/json/?fields=countryCode' 2>/dev/null | jq -r '.countryCode // ""' 2>/dev/null)"
[[ -z "$country" || "$country" == "null" ]] && country="??"

# Fetch weather from wttr.in
Expand Down Expand Up @@ -317,9 +317,9 @@ if [[ "${CFG_STYLE:-banner}" == "banner" ]]; then
frac_g=$(( g2 + (g3 - g2) * j / half2 ))
frac_b=$(( b2 + (b3 - b2) * j / half2 ))
fi
accent_line+="\033[38;2;${frac_r};${frac_g};${frac_b}m━"
accent_line+=$'\033'"[38;2;${frac_r};${frac_g};${frac_b}m━"
done
printf '%b\033[0m\n' "$accent_line"
printf '%s\n' "${accent_line}${C_RESET}"
fi
fi

Expand Down Expand Up @@ -359,7 +359,7 @@ if [[ -n "$worktree_name" ]]; then
line1+=" ${C_MAGENTA}[${worktree_name}]${C_RESET}"
fi

printf '%b\n' "$line1"
printf '%s\n' "$line1"

# ---------------------------------------------------------------------------
# LINE 2: Git branch + stats | Lines added/removed
Expand Down Expand Up @@ -401,7 +401,7 @@ if [[ "$CFG_SHOW_GIT" == true ]]; then
fi
fi

printf '%b\n' "$line2"
printf '%s\n' "$line2"
fi
fi

Expand Down Expand Up @@ -441,7 +441,7 @@ if [[ "$CFG_SHOW_TOKENS" == true ]]; then
line3+=" ${used_pct_int}%/${window_fmt}"
line3+="${CFG_SEPARATOR}${input_fmt} in ${output_fmt} out ${cache_fmt} hit"

printf '%b\n' "$line3"
printf '%s\n' "$line3"
fi

# ---------------------------------------------------------------------------
Expand All @@ -466,7 +466,7 @@ if [[ "$CFG_SHOW_COST" == true ]]; then
fi
line4+="${CFG_SEPARATOR}${duration_fmt}"

printf '%b\n' "$line4"
printf '%s\n' "$line4"
fi

# ---------------------------------------------------------------------------
Expand All @@ -492,7 +492,7 @@ if [[ "$CFG_SHOW_WEATHER" == true ]]; then
line5+=" ${w_temp}"
fi

printf '%b\n' "$line5"
printf '%s\n' "$line5"
fi
fi

Expand Down
Loading