Audit Go code for security vulnerabilities — hardcoded secrets, injection risks (SQL, command, template, path), missing input validation at trust boundaries, insecure defaults, auth gaps, sensitive data exposure, unsafe package usage, and weak crypto. Use when: reviewing Go code before deployment, auditing trust boundaries, preparing for a security review, onboarding third-party integrations, or hardening an application.
Install
npx skillscat add skyosev/agent-skills/security-hunter-go Install via the SkillsCat registry.
Security Hunter
Audit code for security vulnerabilities — places where untrusted input flows into sensitive operations without
validation, secrets are embedded in source, defaults are permissive, or auth checks are missing. The goal: every
trust boundary validates its inputs, no secrets live in code, and the principle of least privilege is applied
throughout.
When to Use
- Reviewing code before production deployment
- Auditing trust boundaries (API endpoints, gRPC handlers, webhooks, CLI input)
- Preparing for a formal security review or pen test
- Onboarding third-party integrations or new external data sources
- Hardening an application after a security incident
Core Principles
Trust boundaries are the perimeter. Every point where external data enters the system — HTTP requests, gRPC
calls, file uploads, environment variables, database reads, third-party API responses — is a trust boundary. Input
must be validated, sanitized, or escaped before it flows into operations that could be exploited.Secrets belong in the environment, never in code. API keys, passwords, tokens, certificates, and connection
strings must come from environment variables, secret managers, or encrypted config — never from source files,
comments, or committed config.Default to deny. Defaults should be restrictive: CORS origins explicit not
*, auth required not optional,
permissions minimal not broad. Permissive defaults are the most common class of security misconfiguration.Defense in depth. No single validation layer is sufficient. Validate at the boundary, escape at the output,
enforce at the data layer. If one layer fails, another should catch the exploit.Fail closed. When auth/authz checks fail or return errors, the result should be denial, not access. Error paths
must not bypass security controls.
What to Hunt
1. Hardcoded Secrets
API keys, passwords, tokens, or connection strings embedded directly in source code.
Signals:
- String literals matching key patterns:
sk_live_,AKIA,ghp_,Bearer,password= - Connection strings with embedded credentials:
postgres://user:pass@host - Base64-encoded strings in auth headers or config
.envfiles or secret-bearing config committed to the repo- Comments containing credentials ("temporary key: xyz123")
constorvardeclarations with secret-looking values
Action: Move to environment variables or a secret manager. Add the pattern to .gitignore. Rotate any committed
secret immediately — it's already compromised if the repo was ever shared.
2. Injection Vulnerabilities
Untrusted input concatenated into strings that are interpreted as code, queries, or commands.
Signals:
- SQL: string concatenation or
fmt.Sprintfin query strings instead of parameterized queries (db.Query(sql, args...)) - Command injection:
exec.Commandwith user-controlled arguments, especially via shell (sh -c) - Path traversal: user input in
os.Open(),filepath.Join(), or URL construction without canonicalization - Template injection: user input rendered via
text/templatein a web/HTML context (no auto-escaping) instead ofhtml/template. Note:text/templateis fine for non-HTML output (config files, CLI output, code generation) - LDAP injection: user input in LDAP filter strings
- Header injection: user input in HTTP response headers without sanitization
- Regex: user input passed directly to
regexp.Compile(ReDoS risk)
Action: Use parameterized queries, allowlists, filepath.Clean + prefix checking, html/template, or regex
escaping. Never concatenate untrusted input into interpreted strings.
3. Missing Input Validation at Trust Boundaries
Endpoints, handlers, or integration points that accept external data without validation.
Signals:
- HTTP handlers that read
r.Body,r.URL.Query(), orr.FormValue()without validation - gRPC handlers that use request fields without validation
- File upload handlers without type/size/content validation
- Webhook handlers without signature verification (HMAC)
- CLI commands that use
os.Argsor flag values without validation - JSON unmarshalling without struct tag validation or post-unmarshal checks
Action: Add validation at every trust boundary. Validate type, format, range, and length. Reject invalid input
explicitly — don't coerce or default.
4. Insecure Defaults and Configuration
Permissive defaults that weaken security posture when not explicitly overridden.
Signals:
- CORS:
Access-Control-Allow-Origin: *or credentials with wildcard origin - TLS:
InsecureSkipVerify: trueintls.Config - TLS: minimum version not set explicitly. Go 1.18+ defaults to TLS 1.2 minimum, but earlier versions default to
TLS 1.0. Verify the project's minimum Go version; flag only when running Go <1.18 or when compliance requires
explicit configuration - HTTP server without timeouts (
ReadTimeout,WriteTimeout,IdleTimeout) - Cookie: missing
Secure,HttpOnly, orSameSiteattributes - Rate limiting absent on auth endpoints or public APIs
- Debug endpoints or pprof exposed in production
- Permissive file permissions (
os.WriteFilewith0666or0777)
Action: Restrict defaults. Require explicit opt-in for permissive settings with justification.
5. Authentication and Authorization Gaps
Missing or inconsistent auth checks that allow unauthorized access or privilege escalation.
Signals:
- Routes/endpoints without auth middleware where peer routes have it
- Authorization checks that compare user IDs with string equality on user-controlled values
- Role checks using client-provided claims without server-side verification
- Admin functionality accessible by changing a URL parameter or request body field
- JWT validation without audience, issuer, or expiry checks
- Password handling: plain-text storage, weak hashing (MD5, SHA1), missing salting
- Session tokens without expiry, rotation, or revocation
Action: Ensure every endpoint has explicit auth/authz. Check authorization server-side against the session, not
client-provided claims. Use bcrypt/scrypt/argon2 for password hashing.
6. Sensitive Data Exposure
Personal data, secrets, or internal details leaked through logs, errors, responses, or storage.
Signals:
- Logging request bodies, auth headers, or user PII (
log.Printf("%+v", request)) - Error responses that include stack traces, internal paths, or database details
- API responses that return more fields than the client needs (no field filtering)
- Sensitive data in URLs (tokens in query parameters — logged by servers and proxies)
- Errors wrapped with
fmt.Errorfthat include sensitive context sent to clients panicstack traces exposed to clients in production
Action: Strip sensitive fields from logs and error responses. Use separate internal/external error messages. Recover
panics in HTTP middleware and return generic errors.
7. Unsafe Package and CGO Risks
Usage of unsafe, reflect, or CGO that bypasses Go's type safety and memory safety.
Signals:
import "unsafe"outside of performance-critical, well-audited codereflectused to bypass unexported field access- CGO code without input validation at the Go/C boundary
//go:linknamedirectives accessing internal runtime functions//go:nosplit,//go:noescapepragmas without justificationunsafe.Pointerarithmetic for memory manipulation
Action: Audit each usage. Ensure unsafe code is isolated, documented, and justified. Verify CGO boundaries validate
all inputs. Prefer pure Go alternatives where possible.
8. Concurrency Safety Issues
Race conditions, deadlocks, and resource leaks that can be exploited or cause denial of service.
Signals:
- Shared state accessed from multiple goroutines without synchronization (mutex, channel, atomic)
sync.Mutexused inconsistently (locked in some paths but not others)- Goroutines started without cancellation via
context.Context - Unbounded goroutine creation from user input (goroutine bomb / DoS)
- Channel operations that can deadlock under error conditions
sync.WaitGroupwithAddcalled in the wrong goroutine
Action: Use -race detector in tests. Protect shared state with appropriate synchronization. Bound goroutine
creation. Use context.Context for cancellation.
9. Dependency and Supply-Chain Risks
Module dependencies with known vulnerabilities or integrity issues.
Signals:
go.sumnot committed to the repo- Dependencies with known CVEs (check via
govulncheck) replacedirectives ingo.modpointing to local paths in production- Using deprecated or unmaintained modules for security-sensitive operations
- Private modules fetched without
GONOSUMCHECK/GONOSUMDBawareness (sumdb trust model)
Action: Commit go.sum. Run govulncheck. Pin dependency versions. Audit transitive dependencies for
security-sensitive code paths.
Audit Workflow
Phase 1: Map Trust Boundaries
- Resolve audit surface. The prompt may specify the scope as:
- Diff: files changed on the current branch vs base (
main/master) - Path: specific files, folders, or packages
- Codebase: the entire project
If unspecified, default to codebase. For diff mode, resolve the file list:
BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main) SCOPE=$(git diff --name-only $(git merge-base HEAD $BASE)...HEAD)Constrain all subsequent scans to the resolved surface.
- Diff: files changed on the current branch vs base (
- Identify trust boundaries: HTTP handlers, gRPC services, webhook endpoints, CLI argument parsers, config loaders.
- Identify auth/authz middleware and where it's applied.
Phase 2: Scan for Security Signals
EXCLUDE='--glob !**/vendor/** --glob !**/testdata/** --glob !**/*_test.go'
# Hardcoded secrets (common patterns)
rg --pcre2 '(sk_live_|AKIA|ghp_|password\s*=\s*"|secret\s*=\s*")' $EXCLUDE
rg --pcre2 '(postgres|mysql|mongodb)://\w+:\w+@' $EXCLUDE
# SQL injection: string formatting in queries
rg --pcre2 '(Query|Exec|QueryRow)\s*\(\s*(fmt\.Sprintf|".*\+|`.*\+)' --type go $EXCLUDE
rg 'fmt\.Sprintf.*SELECT|fmt\.Sprintf.*INSERT|fmt\.Sprintf.*UPDATE|fmt\.Sprintf.*DELETE' --type go $EXCLUDE
# Command injection
rg 'exec\.Command\s*\(' --type go $EXCLUDE
rg 'os/exec' --type go $EXCLUDE
# Path traversal
rg '(os\.Open|os\.ReadFile|os\.Create|ioutil\.ReadFile)' --type go $EXCLUDE
# Template injection (text/template used in web/HTML context — triage: check if output is served to browsers)
rg '"text/template"' --type go $EXCLUDE
# Insecure TLS
rg 'InsecureSkipVerify\s*:\s*true' --type go $EXCLUDE
# Unsafe package
rg '"unsafe"' --type go $EXCLUDE
rg 'go:linkname' --type go $EXCLUDE
# Weak crypto (math/rand is only a concern in security-sensitive contexts like token generation)
rg '"crypto/md5"|"crypto/sha1"' --type go $EXCLUDE
rg '"math/rand"' --type go $EXCLUDE
# Missing HTTP timeouts
rg 'http\.ListenAndServe|&http\.Server\{' --type go $EXCLUDE
# Sensitive data in logs
rg --pcre2 'log\.\w+\(.*\b(password|token|secret|authorization|cookie)\b' -i --type go $EXCLUDE
# File permissions
rg '0666|0777|os\.ModePerm' --type go $EXCLUDE
# pprof in non-debug code
rg 'net/http/pprof|runtime/pprof' --type go $EXCLUDE
# Regex from user input
rg 'regexp\.(Compile|MustCompile)' --type go $EXCLUDE
# Unbounded goroutines from user input
rg 'go\s+func|go\s+\w+\(' --type go $EXCLUDEPhase 3: Trace Data Flows
For each trust boundary found in Phase 1:
- Does untrusted input pass through validation before reaching sensitive operations?
- Are query parameters, body fields, and headers validated for type, format, and range?
- Could a malicious input reach
exec.Command, a SQL query, or template rendering?
Phase 4: Evaluate Auth Coverage
- List all routes/endpoints. For each: is auth middleware applied?
- Identify authorization logic. Is it server-side? Does it rely on client claims?
- Check session/token management: expiry, rotation, revocation.
Phase 5: Produce Report
Output Format
Save as YYYY-MM-DD-security-hunter-audit-{$LLM-name}.md in the project's docs folder (or project root if no docs folder exists).
# Security Hunter Audit — {date}
## Scope
- Surface: {diff / path / codebase}
- Files: {count or list}
- Exclusions: {list}
## Trust Boundary Map
| # | Boundary | Location | Validation | Auth |
| - | -------- | -------- | ---------- | ---- |
| 1 | POST /api/users | file:line | Struct validation | JWT middleware |
| 2 | Webhook /hooks/stripe | file:line | None | None |
## Findings
### Hardcoded Secrets
| # | Location | Pattern | Severity | Action |
| - | -------- | ------- | -------- | ------ |
| 1 | file:line | `sk_live_...` Stripe key | Critical | Move to env, rotate immediately |
### Injection Risks
| # | Location | Type | Untrusted Input | Action |
| - | -------- | ---- | --------------- | ------ |
| 1 | file:line | SQL injection | `r.URL.Query().Get("id")` in fmt.Sprintf | Use parameterized query |
### Missing Input Validation
| # | Location | Boundary | Input | Action |
| - | -------- | -------- | ----- | ------ |
| 1 | file:line | POST /api/orders | `json.Decode` without validation | Add struct validation |
### Insecure Defaults
| # | Location | Setting | Current | Action |
| - | -------- | ------- | ------- | ------ |
| 1 | file:line | TLS verification | `InsecureSkipVerify: true` | Remove or justify |
### Auth/Authz Gaps
| # | Location | Endpoint | Issue | Action |
| - | -------- | -------- | ----- | ------ |
| 1 | file:line | GET /admin/users | No auth middleware | Add admin auth |
### Sensitive Data Exposure
| # | Location | Data | Channel | Action |
| - | -------- | ---- | ------- | ------ |
| 1 | file:line | Auth token | log.Printf | Remove from logs |
### Unsafe/CGO Risks
| # | Location | Pattern | Risk | Action |
| - | -------- | ------- | ---- | ------ |
| 1 | file:line | `unsafe.Pointer` arithmetic | Memory corruption | Audit and document |
### Concurrency Safety
| # | Location | Pattern | Risk | Action |
| - | -------- | ------- | ---- | ------ |
| 1 | file:line | Shared map without mutex | Race condition | Add sync.RWMutex |
### Dependency / Supply-Chain Risks
| # | Location | Issue | Severity | Action |
| - | -------- | ----- | -------- | ------ |
| 1 | go.mod | No go.sum committed | High | Commit go.sum |
## Recommendations (Priority Order)
1. **Critical**: {hardcoded secrets to rotate, injection vulnerabilities, auth bypasses}
2. **Must-fix**: {missing input validation, insecure defaults, auth gaps, dependency CVEs}
3. **Should-fix**: {sensitive data exposure, unsafe usage, concurrency issues, supply-chain hygiene}Operating Constraints
- No code edits. This skill produces an audit report only. Implementation is a separate step.
- Scope: security vulnerabilities only. Do not flag type safety (→ invariant-hunter-go), type design (→ type-hunter-go),
structural complexity (→ simplicity-hunter-go), package boundary issues (→ boundary-hunter-go), interface design
(→ solid-hunter-go), missing documentation (→ doc-hunter-go), test quality (→ test-hunter-go), or cosmetic style
(→ slop-hunter-go). If a finding doesn't answer "could this be exploited?", it doesn't belong here. - Evidence required. Every finding must cite
file/path.go:linewith the exact code. - Severity matters. Use Critical / Must-fix / Should-fix. A hardcoded production secret is not the same severity
as a missingSameSitecookie attribute. Prioritize accordingly. - Context over pattern-matching.
regexp.MustCompilewith a hardcoded pattern is fine.regexp.Compile(userInput)
is a ReDoS risk. Grep finds both — judgment separates them. - No false confidence. This audit catches common patterns, not all vulnerabilities. It is not a substitute for
professional penetration testing or automated security scanning.