Skip to content

Commit 5fa9e7e

Browse files
committed
fix(cli): reject tssvc host endpoints for clients
Disallow tssvc:// for exec/console host resolution and return a clear listen-only error. Add integration tests for both commands.
1 parent 1d3b961 commit 5fa9e7e

File tree

3 files changed

+48
-0
lines changed

3 files changed

+48
-0
lines changed

internal/cli/cli.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ func (e *ExecCommand) Run(ctx *runtimeContext) error {
207207
if err != nil {
208208
return err
209209
}
210+
if err := validateClientEndpoint(ep); err != nil {
211+
return err
212+
}
210213
var cwd string
211214
if ep.Scheme != "unix" {
212215
if e.Chdir == "" {
@@ -414,6 +417,9 @@ func (c *ConsoleCommand) Run(ctx *runtimeContext) error {
414417
if err != nil {
415418
return err
416419
}
420+
if err := validateClientEndpoint(ep); err != nil {
421+
return err
422+
}
417423
var cwd string
418424
if ep.Scheme != "unix" {
419425
if c.Chdir == "" {
@@ -734,6 +740,13 @@ func resolveBackendName(requested, configuredDefault string) string {
734740
return "firecracker"
735741
}
736742

743+
func validateClientEndpoint(ep endpoint.Endpoint) error {
744+
if ep.Scheme != "tssvc" {
745+
return nil
746+
}
747+
return errors.New("tssvc:// endpoints are listen-only; use https://<service>.<your-tailnet>.ts.net for --host")
748+
}
749+
737750
func mergeFirecrackerConfig(cwd string, e *ExecCommand, cfg runtimeconfig.Config) backend.FirecrackerConfig {
738751
out := backend.FirecrackerConfig{
739752
BinaryPath: cfg.Backends.Firecracker.BinaryPath,

internal/cli/console_integration_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,20 @@ func TestConsoleIntegrationInterruptCancelsExecution(t *testing.T) {
203203
t.Fatalf("unexpected console exit code: got %d want %d (err=%v)", got, want, outcome.err)
204204
}
205205
}
206+
207+
func TestConsoleRejectsTailscaleServiceListenEndpointAsHost(t *testing.T) {
208+
outcome := runConsoleWithCapture(ConsoleCommand{
209+
Host: "tssvc://cleanroom",
210+
}, "", runtimeContext{
211+
CWD: t.TempDir(),
212+
})
213+
if outcome.cause != nil {
214+
t.Fatalf("capture failure: %v", outcome.cause)
215+
}
216+
if outcome.err == nil {
217+
t.Fatal("expected host validation error")
218+
}
219+
if !strings.Contains(outcome.err.Error(), "listen-only") {
220+
t.Fatalf("expected listen-only host error, got %v", outcome.err)
221+
}
222+
}

internal/cli/exec_integration_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,21 @@ func TestParseSandboxID(t *testing.T) {
442442
t.Fatalf("expected empty sandbox id for invalid input, got %q", got)
443443
}
444444
}
445+
446+
func TestExecRejectsTailscaleServiceListenEndpointAsHost(t *testing.T) {
447+
outcome := runExecWithCapture(ExecCommand{
448+
Host: "tssvc://cleanroom",
449+
Command: []string{"echo", "hi"},
450+
}, runtimeContext{
451+
CWD: t.TempDir(),
452+
})
453+
if outcome.cause != nil {
454+
t.Fatalf("capture failure: %v", outcome.cause)
455+
}
456+
if outcome.err == nil {
457+
t.Fatal("expected host validation error")
458+
}
459+
if !strings.Contains(outcome.err.Error(), "listen-only") {
460+
t.Fatalf("expected listen-only host error, got %v", outcome.err)
461+
}
462+
}

0 commit comments

Comments
 (0)