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
3 changes: 2 additions & 1 deletion deploy/local/install-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion deploy/local/install-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/security-remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ controls.
- **FAIL IF** the unset default of `DORMOUSE_BIND_HOST` in `relay/src/config.ts` stops being `undefined` — listen on every interface, what a container wants, where the namespace is the boundary — or if `relay/test/bind-host.test.mjs` stops spawning the real entrypoint to prove the plaintext port is unreachable off-loopback when it *is* set.
- **FAIL IF** any installer stops refusing to rewrite a `DORMOUSE_ORIGIN` that no longer matches the node's DNS name.
- **FAIL IF** any installer stops refusing to run with elevated privileges — `id -u` on macOS and Linux, the `Administrator` role check on Windows (rationale).
- **FAIL IF** an installer or `manage` names `tailscale funnel` or `AllowFunnel` at all — invoking it, judging its state, or changing it all begin there, and public reachability must exercise the application controls rather than become a forbidden deployment state. Held by `scripts/deploy-lint.mjs` as its one `forbidden` rule (rationale).
- **FAIL IF** an installer or `manage` names `tailscale funnel` or `AllowFunnel` at all — invoking it, judging its state, or changing it all begin there, and public reachability must exercise the application controls rather than become a forbidden deployment state. Held by `scripts/deploy-lint.mjs` (rationale).
- **FAIL IF** any decision taken on Tailscale CLI or listener output is reached by piping that output into `grep -q`, or into a `head -1` that exits first; every such search is over text captured first, in a helper as much as inline (rationale).
- **FAIL IF** any decision about whether Serve maps `/` to us — the install-time conflict gate, `manage verify`, and the uninstall that turns Serve off — is not additionally scoped to the root line with the port right-bounded. The post-mutation `SERVE_AFTER` assertion is the one deliberate exception (rationale).
- **FAIL IF** `scripts/installer-verify-test.mjs` stops driving `has_off_loopback` and `serve_state` over inputs larger than the pipe buffer, or stops pinning `serve_proxies_root`'s root scoping and port bound. `scripts/deploy-lint.mjs` holds that helper's `<<<` pattern and counts its consumers; `serve_root_target` is held by neither on purpose (rationale).
Expand Down
14 changes: 14 additions & 0 deletions scripts/deploy-lint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading