fix: v0.5.0 security and robustness fixes from the 2026-10-07 review
- Ask Claude: strip control characters, send as bracketed paste (closes #2) - end: forget before kill, so ending from inside a session leaves no ghost (closes #3) - reconnects use `attach --existing` and never resurrect ended/renamed sessions (closes #4) - repo: git --no-optional-locks, no safe.directory override, refresh on connect/bell/60s (closes #5) - uploads: unique names via mktemp, cleanup trap on hangup (closes #6) - clipboard: write-only OSC 52 provider, tmux allow-passthrough off (closes #7) - uploads: size check up front, streamed slices, idle timeout (closes #9) - --list: path last in TSV rows, tolerant numbers (closes #10) - tokens via curl config on stdin; userinfo stripped from remote URLs (closes #11) - ttyd max clients 32 and at most 2 concurrent side-channel calls (#8; BroadcastChannel polling still open) - tmux: fixed terminal-features index (no duplicates on re-source), no status-right clock Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -295,6 +295,58 @@ claude_args() {
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "end run from inside the session leaves no ghost registry entry (#3)" {
|
||||
start_server
|
||||
devdesk new alpha
|
||||
tmux -S "$DEVDESK_SOCKET" send-keys -t =alpha:shell "$REPO/bin/devdesk end alpha" Enter
|
||||
for _ in $(seq 1 50); do
|
||||
tmux -S "$DEVDESK_SOCKET" has-session -t =alpha 2>/dev/null || break
|
||||
sleep 0.1
|
||||
done
|
||||
run tmux -S "$DEVDESK_SOCKET" has-session -t =alpha
|
||||
[ "$status" -ne 0 ]
|
||||
[ "$(jq -r '.sessions | has("alpha")' "$DEVDESK_STATE_DIR/registry.json")" = false ]
|
||||
}
|
||||
|
||||
@test "attach --existing refuses sessions that were ended or renamed (#4)" {
|
||||
start_server
|
||||
run devdesk attach --existing ghost
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"no longer exists"* ]]
|
||||
run devdesk attach --existing ../x
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "same-second uploads of the same name get distinct files (#6)" {
|
||||
printf 'one' | base64 -w0 >"$TMP/a.b64"
|
||||
printf 'two' | base64 -w0 >"$TMP/b.b64"
|
||||
p1=$(devdesk attach --upload alpha image.png 3 <"$TMP/a.b64" | tail -n1 | jq -r .path)
|
||||
p2=$(devdesk attach --upload alpha image.png 3 <"$TMP/b.b64" | tail -n1 | jq -r .path)
|
||||
[ "$p1" != "$p2" ]
|
||||
[ "$(cat "$p1")" = one ]
|
||||
[ "$(cat "$p2")" = two ]
|
||||
[[ $p1 == *-image.png ]]
|
||||
}
|
||||
|
||||
@test "a tab in a project path does not break --list (#10)" {
|
||||
mkdir -p "$DEVDESK_PROJECTS_ROOT/odd name"
|
||||
start_server
|
||||
run devdesk attach --list
|
||||
[ "$status" -eq 0 ]
|
||||
jq -e '.projects | map(.dir) | any(endswith("odd\tname"))' <<<"$output" >/dev/null
|
||||
}
|
||||
|
||||
@test "repo hides credentials embedded in the remote URL (#11)" {
|
||||
export DEVDESK_GITEA_URL=http://127.0.0.1:9
|
||||
repo="$DEVDESK_PROJECTS_ROOT/alpha"
|
||||
git -C "$repo" init -q -b main
|
||||
git -C "$repo" remote add origin https://jared:hunter2@example.com/x/y.git
|
||||
run devdesk repo alpha
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(jq -r .remote <<<"$output")" = https://example.com/x/y.git ]
|
||||
[[ $output != *hunter2* ]]
|
||||
}
|
||||
|
||||
@test "web actions run through attach and answer with JSON" {
|
||||
start_server
|
||||
run devdesk attach --new web1 alpha
|
||||
|
||||
@@ -2,7 +2,7 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
Client, FlowControl, decodeServer, encodeBinaryInput, encodeInit, encodeInput, encodeResize,
|
||||
parseListOutput, parseTitle, sessionFromSearch, slugify, stripAnsi, tokenUrl, wsUrl,
|
||||
parseListOutput, parseTitle, pasteSafe, sessionFromSearch, slugify, stripAnsi, tokenUrl, wsUrl,
|
||||
} from '../web/src/protocol.js';
|
||||
|
||||
const text = (bytes) => new TextDecoder().decode(bytes);
|
||||
@@ -80,3 +80,9 @@ test('flow control pauses after highWater pending writes and resumes below lowWa
|
||||
assert.equal(flow.release(), null); // 1
|
||||
assert.equal(flow.release(), Client.RESUME); // 0
|
||||
});
|
||||
|
||||
test('untrusted text loses control characters before it is typed (#2)', () => {
|
||||
assert.equal(pasteSafe('ok\nrm -rf /\r\x1b[31mred\x07'), 'ok rm -rf / [31mred ');
|
||||
assert.equal(pasteSafe('plain text'), 'plain text');
|
||||
assert.equal(pasteSafe(null), '');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user