Day 81 — Security checklist
Day 81 — Security checklist
Stage VIII · ~3h
Goal: Run a structured security pass over your Go service/CLI—fix or ticket the top issues—and leave a checklist artifact suitable for the capstone evidence pack. Baseline Go 1.26.
Why this day exists
govulncheck (Day 72) is necessary but not sufficient. Most real incidents involve:
- Broken authZ (IDOR)
- Injection (SQL, command)
- Secret leakage
- Overexposed debug endpoints
- Insecure defaults left from “dev mode”
This is a checklist day, not a crypto course. Capstone Day 89 will re-run the high-signal items. Day 80 load results should inform which timeouts and limits you tighten today.
Theory 1 — Threat-oriented checklist for this volume
Work top to bottom. Mark each item PASS / GAP / N/A with a one-line note.
A. Transport & network
- TLS terminated correctly in real deploy (awareness from Day 60)
- No plain secrets on public interfaces
- pprof not public (Day 65)—localhost or build-tag only
/metricsnetwork-restricted or acceptable risk documented
- Timeouts on server and clients (
ReadHeaderTimeout, outbound ctx)
srv := &http.Server{
Addr: cfg.Addr,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}Outbound:
client := &http.Client{Timeout: 10 * time.Second}
// or per-request context.WithTimeoutC. Input & data
- SQL via placeholders only (
$1/?)—never string concat
os/execwith fixed binaries +CommandContext; nosh -c+ user string
- Request body size limits
- Strict content-type for JSON APIs (at least on mutating routes)
- Path traversal checked if serving files
- Upload / batch limits (Day 56 batch max)
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB
if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
// 415 or 400 per your policy
}D. Secrets & config
- No secrets in git history (scan quickly)
.envgitignored;.env.exampleonly
- Logs redact DSNs/tokens
- CI secrets via platform secret store
- JWT/signing keys from env, min entropy for lab
E. Dependencies & supply chain
govulncheck ./...clean or accepted with expiry
- Minimal module graph (no abandoned critical deps)
replacedirectives reviewed
- Toolchain is 1.26.x (stdlib patches)
F. Container & runtime
- Non-root user
- Read-only root FS where possible
- Dropped capabilities (K8s securityContext awareness)
GOMEMLIMITaligned with memory limit
- No privileged docker flags in compose notes
G. Abuse & reliability as security
- Timeouts everywhere (connection pileup is an availability attack)
- Rate limit or at least document absence on login (stretch)
- Panic recovery in HTTP boundary without swallowing auth bugs
- Graceful shutdown (Day 78) reduces half-open weirdness
- Load-test cliff notes linked from Day 80
Theory 2 — SQL injection quick proof
Bad:
q := "SELECT * FROM users WHERE name = '" + name + "'"
row := db.QueryRow(q) // neverGood:
row := db.QueryRowContext(ctx, `SELECT id, name FROM users WHERE name = ?`, name)If you find concatenation—fix today. Grep assist:
rg -n 'Query(Context)?\([^)]*\+|Exec(Context)?\([^)]*\+' --glob '*.go' || true
rg -n 'fmt\.Sprintf\(`.*(SELECT|INSERT|UPDATE|DELETE)' --glob '*.go' || trueTheory 3 — Command injection quick proof
Bad:
exec.Command("sh", "-c", "grep "+userInput+" /var/log/app.log")Good:
cmd := exec.CommandContext(ctx, "/usr/bin/grep", "--", userInput, "/var/log/app.log")
cmd.Path = "/usr/bin/grep" // fixed binaryPrefer pure Go implementations over shelling out. If you must shell out, fixed argv only—no string-built scripts.
Theory 4 — Security headers & CORS (light)
For browser-facing responses:
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
// CSP when you have a real frontend storyAPIs still benefit from careful CORS—default deny. Never combine Access-Control-Allow-Origin: * with credentialed requests.
// explicit allowlist sketch
if origin := r.Header.Get("Origin"); allowed[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Vary", "Origin")
}Theory 5 — Debug endpoint exposure matrix
| Endpoint | Safe default |
|---|---|
/debug/pprof/* |
127.0.0.1 only or build tag debug |
/metrics |
private network; no secrets in labels |
/healthz |
OK public (no sensitive data) |
/readyz |
OK public; do not leak DSNs in body |
| Error JSON | No stack traces to clients |
// separate mux/server for pprof
go http.ListenAndServe("127.0.0.1:6060", nil) // pprof registered on DefaultServeMux in import side effect — prefer dedicated muxPrefer a dedicated debug server mux over hanging pprof on the public :8080 handler.
Theory 6 — Secret hygiene workflow
# triage carefully — expect false positives in tests/docs
rg -i 'api[_-]?key|secret|password\s*=|BEGIN (RSA |OPENSSH )?PRIVATE' \
--glob '!**/*_test.go' \
--glob '!**/testdata/**' || true| Finding | Action |
|---|---|
| Real secret in tree | Rotate; remove; rewrite git history if pushed |
| Example value in docs | Use obviously fake (sk_test_xxx) |
| Test password | Fine if non-production |
.gitignore must include .env, *.pem, credentials.json patterns you actually use.
Worked example — SECURITY-CHECKLIST.md
# Security checklist — day81
Target: myservice @ <git sha>
Date:
Go: go1.26.x
## Scores
| Area | Status | Notes |
|------|--------|-------|
| Timeouts | PASS | ReadHeaderTimeout set |
| Authn | PASS | Bearer on /v1/* |
| Authz | GAP | list endpoint returns all users — ticket |
| SQL | PASS | placeholders only |
| Body limits | PASS | MaxBytesReader 1MiB |
| pprof | PASS | localhost only |
| metrics | PASS | private net note |
| govulncheck | PASS | clean (see SECURITY-SCAN.md) |
| Secrets | PASS | no .env committed |
| Container | PASS | nonroot distroless |
| Load cliffs | NOTE | see LOAD.md — pool at 200 RPS |
## Top fixes completed today
1. MaxBytesReader on JSON routes
2. ReadHeaderTimeout on http.Server
## Accepted risks
1. /metrics on same port — private network only — review: YYYY-MM-DD
## Tickets / follow-ups for capstone
1. IDOR on list endpointLab 1 — Walk the full checklist
Suggested workspace: primary service/CLI.
- Copy the Theory 1 sections into
SECURITY-CHECKLIST.md.
- Mark PASS / GAP / N/A with notes.
- Link Day 72
SECURITY-SCAN.mdand Day 80LOAD.md.
- Do not skip N/A without a reason (CLI vs service).
Lab 2 — Fix at least two real gaps
Pick high-signal fixes:
| Priority | Example fix |
|---|---|
| P0 | SQL concat → placeholders |
| P0 | pprof public → localhost |
| P1 | Missing ReadHeaderTimeout |
| P1 | Missing MaxBytesReader |
| P1 | Auth bypass default on |
| P2 | Security headers on browser routes |
| P2 | Redact Authorization in logs |
go test ./... -count=1
govulncheck ./...Lab 3 — Re-scan supply chain + secret grep
- Re-run
govulncheck ./....
- Run secret grep; triage hits.
- Confirm
.envignored:git check-ignore -v .env || true.
- Update checklist with results.
Lab 4 — AuthZ spot check (services)
Write one test or manual script:
# user A token creates item
# user B token GET/PUT that item → 403 or 404If you only have a single-user lab, document IDOR as residual risk for multi-tenant future.
Lab 5 — Capstone handoff bullets
In checklist footer:
## Capstone (Days 82–89) must preserve
- Timeouts on http.Server
- Authn on mutating routes
- Placeholder SQL only
- pprof not public
- govulncheck in lint scriptDay 89 harden day will re-open this file—make it honest.
Hour-by-hour suggestion
| Time | Focus |
|---|---|
| 0:00–0:40 | Full checklist pass |
| 0:40–1:40 | Two (or more) concrete fixes |
| 1:40–2:10 | govulncheck + secret grep |
| 2:10–2:40 | AuthZ spot check / residual risks |
| 2:40–3:00 | SECURITY-CHECKLIST.md polish |
Common gotchas
| Gotcha | Fix |
|---|---|
| Checklist theater without fixes | Minimum two concrete fixes |
| Disabling auth in “dev” left on | Config flag default secure |
CORS * with credentials |
Never |
| Trusting client-side role claims | Server-side authz |
| Logging Authorization headers | Redact middleware |
500 bodies with err.Error() |
Generic client message |
| “N/A because CLI” skipping file perms | Still check exec and secrets |
| Accepting metrics exposure forever | Review date |
Checkpoint
SECURITY-CHECKLIST.mdcomplete
- ≥2 fixes landed and tested
- govulncheck re-run
- pprof/metrics exposure stated
- Secret grep triaged
- Known gaps listed for capstone
- Capstone handoff bullets written
Commit
git add .
git commit -m "day81: security checklist and fixes"Write three personal gotchas before continuing.
Tomorrow
Day 82 — Capstone design doc: pick option A/B/C and write the plan before code sprawl. Bring security non-negotiables into the design (“auth on mutating routes,” “no public pprof”).