Analyzer::Go::Cli
Inherits CliEndpointSupport < Analyzer::Go::GoEngine < Analyzer < FileHelper < Reference < Object
Surfaces the command-line attack surface of Go programs as cli://
endpoints: one endpoint per (sub)command, with named flags/options
(param_type "flag"), positional arguments ("argument"), and consumed
environment variables ("env"). Covers the stdlib flag package +
os.Args + os.Getenv/os.LookupEnv, plus the cobra, urfave/cli,
pflag, go-arg, go-flags, kong, kingpin and mitchellh/cli ecosystems.
This is a line-scan analyzer (the house style for non-tree-sitter Go
adapters, e.g. fasthttp). Endpoints are merged by URL across files so a
root command whose flags are registered in a separate init() block
collects them all under a single cli://<binary> entry.
Constants
--- builtin flag / argv -------------------------------------------------
The flag name is always the FIRST quoted string in the call: the *Var
forms put the destination pointer (unquoted) first, then the name.
Five markers OR-ed as a standalone boolean gate for cli_parse_point?
below — one precompiled Regex.union (PCRE2 JIT) replaces the
separate content.includes? scan each used to make over the same
buffer. kong/kingpin/mitchellh are deliberately excluded (see the
comment in cli_parse_point?).
--- cobra ---------------------------------------------------------------
Any of these in a file means it participates in the CLI surface.
FRAMEWORK_IMPORTS.any? { |m| content.includes?(m) } re-scans the
whole file once per marker (used at two call sites below); one
precompiled Regex.union (PCRE2 JIT) checks all nine in a single pass.
--- go-arg / go-flags struct tags ---------------------------------------
An HTTP listen call in the same file means env reads are most likely server config, not a CLI surface — suppress raw env there.
--- kingpin (fluent Flag/Arg/Command builder) ---------------------------
app := kingpin.New(...) seeds the root receiver; cmd := app.Command(...)
(or sub := cmd.Command(...) for nesting) maps a new receiver var onto
a command URL, and .Flag(...)/.Arg(...) calls on a known receiver
attribute a param to that command — never to a sticky "current" command.
Per-framework matchers for the scan dispatch below, which asks the
same questions again once cli_evidence? has let a file through.
--- kong (struct-tag CLI) ------------------------------------------------
Root/subcommand fields are declared as struct fields; a cmd:"" tag
marks a field as a subcommand whose own flags/args live in that
field's named struct type, an arg:"" tag marks a positional, and an
env:"" tag additionally binds the field to an environment variable.
--- mitchellh/cli (Commands map of factories) ----------------------------
c.Commands = map[string]cli.CommandFactory{"name": func() (cli.Command, error) { return &FooCommand{}, nil }} maps each map key to the
concrete command type it instantiates; that type's own Run method is
then scanned (scoped to its own body) for flag.FlagSet-style
*Var registrations and raw env reads.
cmd := &DeployCommand{} followed later (within the SAME closure) by
return cmd, nil — the idiomatic form used whenever the command needs
field initialization, as common as the single-expression return &X{}.
--- env -----------------------------------------------------------------
A Go module can hold many commands — cmd/foo, services/bar/cmd —
and each package main directory is a separate binary. Naming every
one of them after the module merged unrelated programs' flags into a
single cli://<module> endpoint. Library packages keep the module
name, so the cross-file merge this analyzer is built around still
works for the helper files a command pulls its flags from.
--- pflag (when used without cobra) -------------------------------------
Cheap pre-gate applied to the RAW file, before the comment strip.
GoEngine.strip_comments materialises an Array(Char) of the whole
file and rebuilds it character by character; running it on every
.go file in a monorepo — 15k of them in kubernetes, of which a
couple of hundred have any CLI surface — was the whole cost of this
analyzer. Stripping only ever replaces characters with ' ' or
'\n' and never changes the character count, so a literal with no
whitespace in it can appear in the stripped text only if it already
appears, unchanged, in the raw text. Every branch of cli_evidence?
requires one of these literals verbatim (FLAG_IMPORT_RE is the
literal "flag"; OS_ARG_INDEX_RE needs an adjacent os.Args), so
a file that misses all of them cannot pass cli_evidence? after the
strip either. Files that do match still go through the full,
comment-aware gate below.
A bare unindented } closes the top-level struct/func block it
belongs to (the gofmt convention this line-scan relies on for
kong's type X struct {} and mitchellh/cli's func (...) Run(...) {}
scoping below).
--- urfave/cli ----------------------------------------------------------
Class methods
Instance methods
Instance-side view of the same declaration. The per-file rescues live on
this base class, which has no way to name the analyzer that is running
inside them, so a skipped file could not be attributed to a tech.
Deriving it from analyzer_for keeps the name written exactly once.