From 4631fbf715d0b75fefa155f3ee8cbe1dc1ff1d5f Mon Sep 17 00:00:00 2001 From: dormouse-bot <287024035+dormouse-bot@users.noreply.github.com> Date: Thu, 1 Oct 2026 12:30:25 +0000 Subject: [PATCH] Capture ts ip -4 before taking its first line in manage verify Both Unix installers piped Tailscale CLI output into head -1 for the plaintext-reachability probe, the shape security-remote.md's Network posture forbids. They now capture first and cut the first line with a parameter expansion, and deploy-lint forbids the piped shape. Refs #873 --- deploy/local/install-linux.sh | 3 ++- deploy/local/install-macos.sh | 3 ++- scripts/deploy-lint.mjs | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/deploy/local/install-linux.sh b/deploy/local/install-linux.sh index 2f849e000..7987e44bf 100755 --- a/deploy/local/install-linux.sh +++ b/deploy/local/install-linux.sh @@ -1128,7 +1128,8 @@ cmd_verify() { fi local tsip - tsip="$(ts ip -4 2>/dev/null | head -1 || true)" + tsip="$(ts ip -4 2>/dev/null || true)" + tsip="${tsip%%$'\n'*}" if [ -n "$tsip" ]; then if http_ok "http://$tsip:$PORT/api/hello" 3; then fail "plaintext port $PORT is reachable on the Tailscale IP $tsip" diff --git a/deploy/local/install-macos.sh b/deploy/local/install-macos.sh index ce893f62f..0d5e903ad 100755 --- a/deploy/local/install-macos.sh +++ b/deploy/local/install-macos.sh @@ -899,7 +899,8 @@ cmd_verify() { fi local tsip - tsip="$(ts ip -4 2>/dev/null | head -1 || true)" + tsip="$(ts ip -4 2>/dev/null || true)" + tsip="${tsip%%$'\n'*}" if [ -n "$tsip" ]; then if curl -s --max-time 3 -o /dev/null "http://$tsip:$PORT/api/hello" 2>/dev/null; then fail "plaintext port $PORT is reachable on the Tailscale IP $tsip" diff --git a/scripts/deploy-lint.mjs b/scripts/deploy-lint.mjs index 00ecb24a9..9435dab2d 100644 --- a/scripts/deploy-lint.mjs +++ b/scripts/deploy-lint.mjs @@ -337,6 +337,20 @@ export const RULES = [ Windows: /(?:\btailscale|\bInvoke-Tailscale)\b[^\n]{0,20}funnel|AllowFunnel/i, }, }, + { + // A `head -1` or `grep -q` that exits before the CLI finishes writing gets + // it killed by SIGPIPE, so the decision rides on a race; capture first, + // then search the captured text. `ts ip -4 | head -1` sat in both + // `manage verify`s while every rule above stayed green. + rule: 'Network posture — no Tailscale CLI output is piped into `head` or `grep -q`', + forbidden: true, + violation: 'tsip="$(ts ip -4 2>/dev/null | head -1 || true)"', + patterns: { + macOS: /(?:\btailscale|\bts)\b[^\n|]*\|\s*(?:head\b|grep\s+-\w*q)/, + Linux: /(?:\btailscale|\bts)\b[^\n|]*\|\s*(?:head\b|grep\s+-\w*q)/, + }, + skip: { Windows: 'every Tailscale decision is a `-match` over a string already captured from `Invoke-Tailscale`, so there is no pipeline to take SIGPIPE' }, + }, { // Anchored on the three paths that matter. A bare `chmod 0700` also matches // `run-relay`, `manage` and the probe state dir, and `Protect-Path` has