From 3d497665c979394e1221a69696b15ffcd6679283 Mon Sep 17 00:00:00 2001 From: Praneeth Ravuri Date: Wed, 29 Jul 2026 16:50:19 -0500 Subject: [PATCH] fix: tetherd silently ignored unknown flags and arguments tetherd never inspected os.Args, so any flag or positional argument -- typo, --help, a stray word -- was dropped and the daemon started normally anyway. Found while testing v0.1.0: `tetherd -h` bound the default socket and hung instead of printing usage. parseArgs rejects anything on the command line via the stdlib flag package: -h/--help now print usage and exit 0, anything else exits 2. Co-Authored-By: Claude Sonnet 5 --- cmd/tetherd/main.go | 34 ++++++++++++++++++++++++++++++++++ cmd/tetherd/main_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/cmd/tetherd/main.go b/cmd/tetherd/main.go index 7a3cfb0..8fd5aa4 100644 --- a/cmd/tetherd/main.go +++ b/cmd/tetherd/main.go @@ -3,6 +3,10 @@ package main import ( "context" + "errors" + "flag" + "fmt" + "io" "log" "net" "os" @@ -15,12 +19,42 @@ import ( "github.com/praneethravuri/tether/pkg/protocol" ) +const usage = `usage: tetherd + +tetherd takes no flags or arguments. Configure it with TETHER_SOCK and +TETHER_DB; see the README for the full list of environment variables.` + +// parseArgs rejects anything on the command line: tetherd takes no flags or +// arguments. Without this, an unrecognized flag like a stray "-h" was +// silently ignored and the daemon started anyway, bound to the default +// socket -- there was no way to tell a typo from a real invocation. +func parseArgs(args []string) error { + fs := flag.NewFlagSet("tetherd", flag.ContinueOnError) + fs.SetOutput(io.Discard) // usage is printed by the caller, once, on our terms + if err := fs.Parse(args); err != nil { + return err + } + if fs.NArg() > 0 { + return fmt.Errorf("unrecognized argument %q", fs.Arg(0)) + } + return nil +} + // version is overridden at build time the same way as cmd/tether's, via // -ldflags "-X main.version=...". Logged at startup since tetherd has no // subcommands to print it on request. var version = "dev" func main() { + if err := parseArgs(os.Args[1:]); err != nil { + if errors.Is(err, flag.ErrHelp) { + fmt.Println(usage) + os.Exit(0) + } + fmt.Fprintf(os.Stderr, "tetherd: %v\n%s\n", err, usage) + os.Exit(2) + } + log.SetFlags(log.LstdFlags | log.Lmsgprefix) log.SetPrefix("tetherd: ") diff --git a/cmd/tetherd/main_test.go b/cmd/tetherd/main_test.go index babec7e..0dcc530 100644 --- a/cmd/tetherd/main_test.go +++ b/cmd/tetherd/main_test.go @@ -3,6 +3,8 @@ package main import ( "context" "encoding/json" + "errors" + "flag" "io" "log" "net" @@ -16,6 +18,40 @@ import ( "github.com/praneethravuri/tether/pkg/protocol" ) +func TestParseArgs(t *testing.T) { + cases := []struct { + name string + args []string + wantErr error // checked with errors.Is; nil means "some error" + wantOK bool + }{ + {name: "no arguments", args: nil, wantOK: true}, + {name: "help flag", args: []string{"-h"}, wantErr: flag.ErrHelp}, + {name: "long help flag", args: []string{"--help"}, wantErr: flag.ErrHelp}, + {name: "unrecognized flag", args: []string{"-x"}}, + {name: "stray positional argument", args: []string{"foo"}}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := parseArgs(tc.args) + switch { + case tc.wantOK: + if err != nil { + t.Fatalf("parseArgs(%v) = %v, want nil", tc.args, err) + } + case tc.wantErr != nil: + if !errors.Is(err, tc.wantErr) { + t.Fatalf("parseArgs(%v) = %v, want %v", tc.args, err, tc.wantErr) + } + default: + if err == nil { + t.Fatalf("parseArgs(%v) = nil, want an error", tc.args) + } + } + }) + } +} + func TestDaemonIsLive_NoSocketAtAll(t *testing.T) { dir := shortTempDir(t) if daemonIsLive(filepath.Join(dir, "does-not-exist")) {