| name | supply-chain-secure-code |
| description | Use when writing Go code that interacts with dependencies, handles credentials, executes commands, or manages configuration. Provides supply chain attack countermeasures at the code level including safe dependency usage, credential handling, subprocess hardening, and runtime integrity patterns. Adapted from Shai-Hulud npm attack lessons. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| user-invocable | true |
Supply Chain Secure Code (Go)
This skill provides Go coding patterns that defend against supply chain attacks at the application code level. While Go's module system is inherently more secure than npm (no lifecycle scripts, mandatory checksums), malicious code within imported packages still executes at runtime.
When to Apply
Apply these guidelines when:
- Importing and using third-party packages
- Handling credentials, tokens, or API keys in code
- Executing external commands (
os/exec)
- Loading configuration from files or environment variables
- Reviewing code for supply chain attack vectors
Threat Model: Go-Specific Attack Vectors
| Attack Vector | Description | Defense |
|---|
| Malicious import | Compromised package executes malicious code when imported | Minimize dependencies, review imports |
init() functions | Execute automatically on import, before main() | Audit init() in dependencies |
go:generate directives | Execute arbitrary commands | Never run go generate on untrusted code |
| cgo | Links native C code, bypasses Go safety | Avoid cgo when possible (CGO_ENABLED=0) |
os/exec in dependencies | Dependencies may spawn processes | Audit dependency code paths |
unsafe package | Bypasses Go type safety | Flag unsafe usage in dependencies |
| Build constraints | Platform-specific code may hide malicious logic | Review all build tags |
NOTE: Go has no lifecycle scripts. go get and go build do NOT execute arbitrary code (unlike npm install). The risk is at runtime when your application imports and calls dependency code.
Credential Handling
Never Hardcode Credentials
const apiKey = "ghp_xxxxxxxxxxxxxxxxxxxx"
var config = Config{
Token: "sk-xxxxxxxxxxxxxxxxxxxx",
}
apiKey := os.Getenv("API_KEY")
if apiKey == "" {
log.Fatal("API_KEY environment variable is required")
}
Credential Validation at Startup
type Config struct {
APIKey string `env:"API_KEY" required:"true"`
DatabaseURL string `env:"DATABASE_URL" required:"true"`
}
func LoadConfig() (*Config, error) {
cfg := &Config{
APIKey: os.Getenv("API_KEY"),
DatabaseURL: os.Getenv("DATABASE_URL"),
}
if cfg.APIKey == "" {
return nil, fmt.Errorf("API_KEY is required")
}
if cfg.DatabaseURL == "" {
return nil, fmt.Errorf("DATABASE_URL is required")
}
return cfg, nil
}
Credential Isolation in Subprocesses
cmd := exec.Command("some-tool", "--flag")
cmd := exec.Command("some-tool", "--flag")
cmd.Env = []string{
"PATH=" + os.Getenv("PATH"),
"HOME=" + os.Getenv("HOME"),
}
Safe Dependency Usage
init() Function Awareness
Go's init() functions execute automatically when a package is imported -- this is the closest equivalent to npm's lifecycle scripts:
func init() {
go exfiltrate(os.Environ())
}
Defense: Audit init() functions in new dependencies:
grep -r "func init()" $(go env GOMODCACHE) 2>/dev/null | head -50
Minimize Import Surface
import "github.com/large-framework/everything"
import "github.com/large-framework/strings"
import "strings"
Avoid Unsafe and Reflect
import "unsafe"
import "reflect"
import "plugin"
Subprocess Security
Validated Command Execution
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo %s", userInput))
cmd := exec.Command("echo", userInput)
if !regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString(userInput) {
return fmt.Errorf("invalid input: %q", userInput)
}
cmd := exec.Command("tool", "--name", userInput)
Environment Isolation
func safeExec(name string, args ...string) *exec.Cmd {
cmd := exec.Command(name, args...)
cmd.Env = []string{
"PATH=/usr/bin:/bin",
"HOME=" + os.Getenv("HOME"),
"LANG=en_US.UTF-8",
}
return cmd
}
Never Download and Execute
resp, _ := http.Get("https://example.com/binary")
os.WriteFile("/tmp/tool", body, 0755)
exec.Command("/tmp/tool").Run()
func verifiedDownload(url, expectedSHA256 string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
hash := sha256.Sum256(data)
actual := hex.EncodeToString(hash[:])
if actual != expectedSHA256 {
return nil, fmt.Errorf("integrity check failed: expected %s, got %s",
expectedSHA256, actual)
}
return data, nil
}
File Access Security
Credential File Protection
func safePath(base, userPath string) (string, error) {
resolved := filepath.Join(base, userPath)
cleaned := filepath.Clean(resolved)
if !strings.HasPrefix(cleaned, filepath.Clean(base)+string(os.PathSeparator)) {
return "", fmt.Errorf("path traversal detected: %s", userPath)
}
return cleaned, nil
}
Network Security
Outbound Request Validation
var allowedHosts = map[string]bool{
"api.example.com": true,
}
func validateURL(rawURL string) (*url.URL, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
if !allowedHosts[u.Hostname()] {
return nil, fmt.Errorf("blocked request to unauthorized host: %s", u.Hostname())
}
blocked := []string{"169.254.169.254", "metadata.google.internal"}
for _, host := range blocked {
if u.Hostname() == host {
return nil, fmt.Errorf("blocked request to metadata endpoint: %s", host)
}
}
return u, nil
}
Build Security
Disable cgo When Not Needed
CGO_ENABLED=0 go build ./...
- name: Build
env:
CGO_ENABLED: "0"
run: go build ./...
Reproducible Builds
go build -trimpath -ldflags="-s -w" ./...
Code Review Checklist
High Priority
Medium Priority
Low Priority (Defense in Depth)
Post-Compromise Detection
grep -r "//go:generate" $(go env GOMODCACHE) 2>/dev/null | \
grep -E "(curl|wget|bash|sh |http)" | head -20
go mod verify
grep -r "func init()" $(go env GOMODCACHE) 2>/dev/null | \
grep -v "_test.go" | head -50
grep -r "os/exec" $(go env GOMODCACHE) 2>/dev/null | \
grep -v "_test.go" | head -50
grep -r "net/http\|net.Dial" $(go env GOMODCACHE) 2>/dev/null | \
grep -v "_test.go" | head -50
gh api repos/{owner}/{repo}/actions/runners --jq '.runners[] | {name, status}'
find .github/workflows -name "*.yml" -newer go.mod
References