Skip to content

chore(security): gosec sweep — fix all Go findings + add CI gate - #509

Closed
lusoris wants to merge 1 commit into
masterfrom
chore/gosec-findings-fix
Closed

lusoris wants to merge 1 commit into
masterfrom
chore/gosec-findings-fix

Conversation

@lusoris

@lusoris lusoris commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • First-pass gosec static-security scan across the Go surface
    (cmd/vmafx-{controller,node,server,operator,mcp,tune}/..., pkg/...,
    gen/go/...). 38 raw findings → 0 in fork-original source
    post-sweep, 10 in generated code excluded via -exclude-generated.
  • One real bug fixed: cmd/vmafx-mcp/impl.go::describeModel accepted a
    caller-supplied name, joined it onto the repo root, and os.Stat-ed
    the result — {"name": "../../../etc/passwd"} would have escaped
    libvmaf.AllowedRoots(). Routed through libvmaf.ValidatePath;
    regression test
    cmd/vmafx-mcp/impl_gosec_test.go::TestDescribeModelRejectsTraversal
    added.
  • CI gate: new gosec step in .github/workflows/go-ci.yml after go vet
    plus make lint-go Makefile target. Future PRs touching Go gate on
    gosec -exclude-generated -quiet ./... returning 0 findings.

Per-rule findings (pre / post)

Rule Severity Pre Post (source) Post (incl. generated)
G103 LOW 4 0 4 (protoc-generated gen/go/vmafx.pb.go::unsafe.Slice)
G104 LOW 1 0 0
G115 HIGH 6 0 6 (cgo trampoline cache for pkg/libvmaf/direct.go)
G202 MEDIUM 1 0 0
G204 MEDIUM 19 0 0
G301 MEDIUM 1 0 0
G304 MEDIUM 5 0 0
G306 MEDIUM 1 0 0
Total 38 0 10 (all in generated / cgo trampolines)

Per-finding fix summary

Real bug — G304 path traversal

  • cmd/vmafx-mcp/impl.go::describeModel — caller-supplied name
    joined to repo root, then opened. Fixed by routing the candidate
    path through libvmaf.ValidatePath so the lookup is bounded to
    libvmaf.AllowedRoots().
  • Regression test
    cmd/vmafx-mcp/impl_gosec_test.go::TestDescribeModelRejectsTraversal
    asserts the reject for ../../../etc/passwd,
    ../../../../etc/shadow, /etc/passwd, /proc/self/environ.

Permissions tightening — G301 / G306

  • cmd/vmafx-tune/cmd/compare.go::writeOutput —
    MkdirAll 0o755 → 0o750, WriteFile 0o644 → 0o600. Reports
    include dataset path identifiers; restrict to the owner.

Unhandled error — G104

  • cmd/vmafx-mcp/impl.go::runVmafScore — outFile.Close() and
    os.Remove(outPath) results now checked. Close failure
    short-circuits the request; remove failure logs to stderr
    (best-effort cleanup).

False positives now correctly cited — G204 / G304 / G202 (22 sites)

Every existing //nolint:gosec ... was rewritten to
// #nosec G<rule> -- <citation>. gosec does NOT parse
golangci-lint nolint directives — the old comments were dead text
that did not suppress anything. Per-site citations name the
validating helper (constant binary name, libvmaf.ValidatePath,
os.CreateTemp, os.MkdirTemp, exec.LookPath).

Touched: cmd/vmafx-mcp/impl.go (15), pkg/storage/fuse_mount.go,
pkg/storage/http_serve.go, pkg/encoder/encoder.go,
pkg/gpu/detect.go, pkg/ai/infer.go, pkg/bisect/bisect.go,
cmd/vmafx-controller/queue/queue.go.

Test additions

  • cmd/vmafx-mcp/impl_gosec_test.go::TestDescribeModelRejectsTraversal
    (1 test, 4 sub-cases) — path-traversal-style names rejected.
  • cmd/vmafx-mcp/impl_gosec_test.go::TestDescribeModelRejectsEmptyName
    — defence-in-depth on the empty-name guard.

Deliverables (CLAUDE.md §12 r11)

  • Research digest: docs/research/gosec-findings-fix-sweep-2026-06-01.md
  • Decision matrix: ADR-0983 Alternatives table
  • AGENTS.md invariant: cmd/vmafx-mcp/AGENTS.md invariant chore(deps): Update dependency openai to >=2.38.0 #7
  • Reproducer: see Test plan
  • Changelog fragment: changelog.d/security/gosec-findings-fix-sweep.md
  • Rebase notes: docs/rebase-notes.md
  • state.md row: T-GOSEC-FINDINGS-FIX-SWEEP-2026-06-01

Test plan

  • CI: Go CI workflow — go vet, the new gosec -exclude-generated
    step, and go test (skip cmd/vmafx-operator — envtest binaries
    not on hosted runner) all green.
  • Local verification:
    gosec -exclude-generated ./...   # Issues: 0
    go vet ./...                     # clean
    go build ./...                   # clean
    go test ./cmd/vmafx-mcp/ -run TestDescribeModelRejects -race -count=1
    
  • Negative reproducer (pre-fix would NOT raise; post-fix must raise):
    // describeModel("../../../etc/passwd") returns
    // 'model "../../../etc/passwd" not found' rather than opening the file.
  • No Netflix golden assertion touched; no SIMD / GPU / cgo file modified
    beyond // #nosec citations.

Refs: ADR-0983.

Co-Authored-By: Claude Opus 4.7 noreply@anthropic.com

🤖 Generated with Claude Code

Run gosec across cmd/vmafx-{controller,node,server,operator,mcp,tune}/...,
pkg/..., and gen/go/... 38 raw findings — 10 in protoc-generated /
cgo trampoline code (excluded via -exclude-generated), 26 in
fork-original source. All 26 source findings resolved; CI gate added
in .github/workflows/go-ci.yml plus make lint-go target.

One real bug: cmd/vmafx-mcp/impl.go::describeModel accepted a
caller-supplied `name` and joined it onto the repo root before
os.Stat-ing the result. A request like {"name": "../../../etc/passwd"}
would have reached /etc/passwd because the candidate path was not
constrained to libvmaf.AllowedRoots(). Routed through
libvmaf.ValidatePath; new regression test
cmd/vmafx-mcp/impl_gosec_test.go::TestDescribeModelRejectsTraversal.

Other fixes:
- G306/G301: cmd/vmafx-tune/cmd/compare.go::writeOutput tightened
  from 0o644 / 0o755 to 0o600 / 0o750.
- G104: runVmafScore's outFile.Close() and os.Remove(outPath)
  errors now checked.
- 22 G204 / G304 / G202 false positives previously suppressed via
  //nolint:gosec (which gosec ignores — it only honours #nosec)
  rewritten to // #nosec G<rule> -- ... per CLAUDE §12 r12.

Deliverables: ADR-0983, docs/research/, changelog.d/security/,
docs/rebase-notes.md, docs/state.md row,
cmd/vmafx-mcp/AGENTS.md invariant #7.

Refs: ADR-0983.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lusoris
lusoris marked this pull request as ready for review May 31, 2026 22:07
@lusoris

lusoris commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

Source conflicts with #505 (vmafx-tune timeouts) on bisect.go + encoder.go. Re-running fresh gosec audit against post-#505 master.

@lusoris lusoris closed this May 31, 2026
@lusoris
lusoris deleted the chore/gosec-findings-fix branch May 31, 2026 22:08
lusoris added a commit that referenced this pull request May 31, 2026
…510)

Re-run of the gosec static-security scan against the post-#505 / post-#509-close
master tip. PR #509 had source conflicts with PR #505 on pkg/bisect/bisect.go
and pkg/encoder/encoder.go; this v2 sweep applies the same security-hardening
fixes while preserving the ctx + per-stage timeout that #505 added.

Findings: 38 raw, 10 in generated/cgo trampoline code (excluded via
-exclude-generated), 26 fork-original. All 26 source findings now discharged.
Post-fix `gosec -exclude-generated ./...` returns 0 issues.

Real bug fixed:
  - G304 in cmd/vmafx-mcp/impl.go::describeModel — joined caller-supplied
    `name` onto repo root and os.Stat-ed the result, allowing
    {"name": "../../../etc/passwd"} to escape libvmaf.AllowedRoots().
    Routed through libvmaf.ValidatePath; regression test at
    cmd/vmafx-mcp/impl_gosec_test.go::TestDescribeModelRejectsTraversal.

Hardening:
  - G306/G301 in cmd/vmafx-tune/cmd/compare.go::writeOutput — tightened to
    0o600 (file) / 0o750 (directory).
  - G104 in cmd/vmafx-mcp/impl.go::runVmafScore — outFile.Close() and
    os.Remove(outPath) return values now checked.
  - G204/G304 ×22 — `//nolint:gosec` comments rewritten to
    `// #nosec G<rule> -- <citation>` (gosec ignores nolint:gosec).
  - G202 in cmd/vmafx-controller/queue/queue.go — verified safe (only
    repeatCommaQ output concatenated; status values bind via placeholders).

CI gate: gosec step in .github/workflows/go-ci.yml + make lint-go target.

Differs from PR #509:
  - pkg/bisect/bisect.go: keeps PR #505's exec.CommandContext +
    scoreTimeout() — just adds the #nosec G204 citation.
  - pkg/encoder/encoder.go: keeps PR #505's exec.CommandContext +
    encodeTimeout()/probeTimeout() — same.

Verification:
  - gosec -exclude-generated -quiet ./... → 0 findings (was 26)
  - go vet ./... clean
  - go build ./... clean
  - go test -race ./cmd/vmafx-mcp/ -run TestDescribeModel → 5/5 pass
  - go test -race ./... (skip vmafx-operator envtest) → all packages OK
    except TestHandleVmafScore_RoutesToDirect + TestHandleDescribeModel_DirectAugments
    in cmd/vmafx-mcp/ which require libvmaf.so and fail identically on master
    (verified via stash + re-run).
  - bash scripts/ci/check-adr-numbering.sh → PASS
  - bash scripts/ci/check-copyright.sh → PASS
  - pre-commit run --files → PASS

Refs ADR-0983.

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant