fix(tailscale): validate ports and quote MSI paths (#11534)

Validated in a combined 10-PR batch worktree off release/v3.8.51 tip.
- Focused test: tests/unit/tailscale-validation.test.ts — 2/2 pass plus 13/13 related Tailscale tests
- typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity gates — all OK
- Full-repo lint: 503 pre-existing problems confirmed identical on the pure release/v3.8.51 tip — unrelated to this diff

⚠️ base-red inherited: #11449

Thanks for rejecting out-of-range Tailscale ports and escaping apostrophes before they reach the Windows MSI installer PowerShell command.
This commit is contained in:
Paco Cartones
2026-08-25 18:12:38 +02:00
committed by GitHub
parent e48ffd38d3
commit 436ce4869f
4 changed files with 27 additions and 7 deletions

View File

@@ -0,0 +1 @@
- Fixed Tailscale tunnel setup to reject ports outside the valid 1-65535 range and safely install Windows MSI packages from paths containing apostrophes.

View File

@@ -6,7 +6,7 @@ import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
export const tailscaleEnableSchema = z.object({
sudoPassword: z.string().optional(),
hostname: z.string().optional(),
port: z.number().int().positive().optional(),
port: z.number().int().min(1).max(65535).optional(),
});
export const tailscaleLoginSchema = z.object({

View File

@@ -1104,6 +1104,11 @@ async function installTailscaleLinux(password: string, onProgress?: (message: st
});
}
export function buildWindowsTailscaleInstallCommand(msiPath: string): string {
const escapedMsiPath = msiPath.replace(/'/g, "''");
return `Start-Process msiexec -ArgumentList '/i','${escapedMsiPath}','TS_NOLAUNCH=true','/quiet','/norestart' -Verb RunAs -Wait`;
}
async function installTailscaleWindows(onProgress?: (message: string) => void) {
const msiUrl = "https://pkgs.tailscale.com/stable/tailscale-setup-latest-amd64.msi";
const msiPath = path.join(os.tmpdir(), "tailscale-setup.msi");
@@ -1125,12 +1130,7 @@ async function installTailscaleWindows(onProgress?: (message: string) => void) {
await new Promise<void>((resolve, reject) => {
const child = spawn(
"powershell",
[
"-NoProfile",
"-NonInteractive",
"-Command",
`Start-Process msiexec -ArgumentList '/i','${msiPath}','TS_NOLAUNCH=true','/quiet','/norestart' -Verb RunAs -Wait`,
],
["-NoProfile", "-NonInteractive", "-Command", buildWindowsTailscaleInstallCommand(msiPath)],
{
windowsHide: true,
stdio: ["ignore", "pipe", "pipe"],

View File

@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import test from "node:test";
import { tailscaleEnableSchema } from "../../src/app/api/tunnels/tailscale/routeUtils.ts";
import { buildWindowsTailscaleInstallCommand } from "../../src/lib/tailscaleTunnel.ts";
test("tailscale enable accepts only valid TCP ports", () => {
assert.equal(tailscaleEnableSchema.safeParse({ port: 1 }).success, true);
assert.equal(tailscaleEnableSchema.safeParse({ port: 65535 }).success, true);
assert.equal(tailscaleEnableSchema.safeParse({ port: 0 }).success, false);
assert.equal(tailscaleEnableSchema.safeParse({ port: 65536 }).success, false);
});
test("Windows installer command escapes apostrophes in MSI paths", () => {
assert.equal(
buildWindowsTailscaleInstallCommand("C:\\Users\\O'Brien\\tailscale-setup.msi"),
"Start-Process msiexec -ArgumentList '/i','C:\\Users\\O''Brien\\tailscale-setup.msi','TS_NOLAUNCH=true','/quiet','/norestart' -Verb RunAs -Wait"
);
});