A command-line tool that parses Go module files (go.mod
) and scans all dependencies for command pattern usage. It identifies and extracts code lines containing specific command patterns like .Command(
, .RunCommand(
, and .Cmd(
.
Bad actors copy popular GitHub projects, add faked stars for credibility and then injected malicious code at runtime. Attackers obfuscate their code but a common pattern is executing the command in the shell. This tool hopefully helps identify unexpected shell access from dependencies of a Golang project.
A typical use case is to run this tool on a project directly after adding a new dependency.
See this discussion on Reddit: Someone copied our GitHub project, made it look more trustworthy by adding stars from many fake users, and then injected malicious code at runtime for potential users.
- Parse
go.mod
files to extract module information - Scan all Go files in dependencies for specific command patterns:
.Command(
.RunCommand(
.Cmd(
- Extract and display the actual lines of code containing these patterns
- Handle replace directives
- Skip test files (
*_test.go
) to focus on implementation code - Optionally skip official Go packages (
golang.org/*
) - Optionally skip user-specified packages
- Optionally no color output
# Clone the repository
git clone https://github.com/Gys/cmdscanner
cd cmdscanner
# Build the application
go build -o cmdscanner
# Scan the first found go.mod file in the current directory or parent directories
./cmdscanner
# Scan a specific go.mod file
./cmdscanner -file /path/to/go.mod
# Also include official Go packages (*.golang.org/*)
./cmdscanner -include-go-official
# Skip specific packages (comma-separated list)
./cmdscanner -skip github.com/modernc.org/libc,github.com/pkg/errors
# By default color output is enabled
./cmdscanner -no-color
Module: github.com/example/myproject
Go version: 1.20
Module cache location: /home/user/go/pkg/mod
Searching for command patterns: .Command(, .RunCommand(, .Cmd(
Skipping test files (*_test.go)
Skipping official Go packages (golang.org/*)
Skipping user-specified packages: github.com/pkg/errors
Summary:
/home/user/go/pkg/mod/github.com/spf13/[email protected]/command.go:142
rootCmd.Command("version", "Print the version number")
/home/user/go/pkg/mod/github.com/spf13/[email protected]/command.go:157
cmd := &Command{Use: "app"}
/home/user/go/pkg/mod/github.com/spf13/[email protected]/cobra.go:120
c := root.Cmd("status", "Show status")
95% of this code and the README.md are generated by Claude 3.7 Sonnet, using Cody in VS Code.
The tool looks for these command patterns:
.Command(
.RunCommand(
.Cmd(
The tool skips all files ending with _test.go
to focus on implementation code.
With the -include-go-official
flag, the tool will include scanning packages from the Go project itself (those with paths starting with golang.org/
).
Use the -skip
flag to specify a comma-separated list of packages to skip during scanning.
By default the code lines are colored yellow. Use the -no-color
flag to disble.
Go stores downloaded packages in a central module cache. The location of this cache is determined by:
- The
GOMODCACHE
environment variable (Go 1.14+) - Fallback:
$GOPATH/pkg/mod
(older Go versions)
You can find your module cache location by running:
go env GOMODCACHE
Module paths in the filesystem are encoded to handle special characters:
- Uppercase letters are converted to '!' followed by the lowercase letter
- The '!' character is converted to '!!'
- Other special characters may also be encoded
The tool handles this encoding automatically using Go's module.EscapePath
function.
Module versions in the filesystem may have special suffixes:
+incompatible
suffix for modules that don't follow semantic versioning- Pseudo-versions like
v0.0.0-20230822171919-f59e071c2a15
The tool handles these special cases when determining installation paths.
Replace directives in go.mod
files can point to:
- Another module version (stored in the module cache)
- A local filesystem path (not in the module cache)
The tool distinguishes between these cases and shows the appropriate location.
The tool recursively scans all .go
files in each dependency's directory:
- It skips directories like
.git
,testdata
, andvendor
- It skips test files (
*_test.go
) - It optionally includes official Go packages (
golang.org/*
) - It searches for the specified command patterns in each Go file
- It extracts and displays the actual lines of code containing these patterns
- It provides a detailed summary of all matching files and code lines at the end
- It groups and counts occurrences by pattern type
To add more command patterns to search for, modify the CommandPatterns
slice in the code:
var CommandPatterns = []string{
`.Command(`,
`.RunCommand(`,
`.Cmd(`,
// Add more patterns here
`.NewCommand(`,
`.AddCommand(`,
}
If your project uses vendoring (go mod vendor
), dependencies are also copied to the vendor
directory in your project. This tool focuses on the module cache, not vendored dependencies.
Go may use a proxy for downloading modules (controlled by the GOPROXY
environment variable). This doesn't affect where packages are stored locally, but it affects how they're downloaded.
Go maintains a checksum database (go.sum
file) to verify the integrity of modules. This is separate from the actual module storage.