Skip to content

Harden untrusted-input handling, refactor check engine, and enforce golangci-lint#19

Merged
alxxjohn merged 6 commits into
mainfrom
harden-perf
Jul 1, 2026
Merged

Harden untrusted-input handling, refactor check engine, and enforce golangci-lint#19
alxxjohn merged 6 commits into
mainfrom
harden-perf

Conversation

@alxxjohn

@alxxjohn alxxjohn commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

A hardening, refactor, and quality pass over the codebase (branch scanned itself the whole way — build/vet/gofmt clean, 476 tests pass, golangci-lint 0 issues and now CI-blocking). Threat model throughout: codeguard runs in CI on untrusted PRs, so repo config and scanned content are attacker-controllable.

135 files changed, +1027/−617.

Hardening (H1–H10)

  • Glob matcher can't panic the scanMatchPattern uses regexp.Compile + a sync.Map cache and returns false (never panics) on a malformed config glob (runner/support/utils.go).
  • base_ref option-injection blocked — validated by ValidateBaseRef (rejects leading -, charset allowlist, keeps the stdin sentinel) before reaching git, with --end-of-options on git diff/show/worktree.
  • Git & govulncheck subprocesses now use exec.CommandContext with contained timeouts and bounded (io.LimitReader) output — no more hangs or OOM on a huge diff.
  • MCP HTTP server sets ReadHeaderTimeout (+Read/Write/Idle) against Slowloris.
  • Panic isolation — each check section runs through safeRun; a panic degrades that section to a warning instead of aborting the whole scan.
  • Leak fixes — triage response body, http_post close, MCP ctx cancel; httpretry now drains before close; GitHub comment client threads ctx + a client timeout.
  • File I/O — cache/config reads capped; dir/file perms tightened to 0750/0600.

Refactor (Q2/Q3/Q8) — behavior-preserving

  • Data-driven section registry replaces the hardcoded dispatch if-ladder (runner/checks/registry.go); all 8 sections covered, ordering + panic-recovery preserved.
  • warnFinding builder collapses 21 hand-written FindingInput struct literals (incl. the three per-language performance scanners and AI-quality emitters). Note: the per-language scanners use genuinely different strategies (Go AST / Python indent / TS brace-depth), so no shared line-scanner was forced.
  • CLI cleanupparseFlags helper + named exit-code constants.

Quality & tooling (Q1/Q4/Q5/Q6/Q7)

  • golangci-lint wired and now enforced — CI runs the 16-linter v2 config as a blocking step (was go vet only). Fixed a latent bug: the config had been on the v1 issues.exclude-rules schema, silently ignored under golangci-lint v2.
  • %w error wrapping; sha1→sha256 for cache/identity keys; removed dead code, redundant conversions, loop-var copies; prealloc where bounded.
  • Production gosec findings driven to zero (justified //nolint on intentional subprocess/file/jitter sites).
  • Regression tests for ValidateBaseRef and the panic-safe MatchPattern (430 → 476 tests).

Config decisions worth a look

  • Revive's documentation/naming style rules (exported, package-comments, unused-parameter) are excluded — the correctness/safety linters gate CI. tests/ is excluded from gosec/errcheck/prealloc/bodyclose (intentional fixtures).
  • The //nolint:contextcheck markers in the git layer are deliberate: the contained timeout is the chosen design; full caller-ctx threading is a tracked follow-up (don't remove the nolint without actually threading ctx).

Knowledge captured

.claude/knowledge/architecture-boundaries.md — the section registry + safeRun, the git/base_ref hardening contract (and why the contextcheck nolints are deliberate), and the golangci-lint enforcement + revive exclusions.

Tracked follow-ups (intentionally not in this PR)

  1. Doc comments on the public pkg/codeguard API (revive exported currently excluded rather than satisfied).
  2. Deeper ctx threading through the git helpers (currently contained timeouts + scoped nolint).

Verification

go build ./... ✓ · go vet ./... ✓ · gofmt -l clean · go test ./... = 476 passed · golangci-lint run = 0 issues.

🤖 Generated with Claude Code

alxxjohn and others added 6 commits June 30, 2026 15:03
Threat model: codeguard runs in CI on untrusted PRs, so repo config and
scanned content are attacker-controllable. This wave closes a set of
hardening gaps and clears the mechanical/security lint backlog.

Hardening:
- glob matcher no longer panics on malformed config patterns (regexp.Compile
  + treat-as-no-match) and memoizes compiled patterns via sync.Map (utils.go)
- validate base_ref before git (reject leading '-', charset allowlist,
  --end-of-options) to block option injection (diff/changed_files/
  diff_command/mcp_tools/ai-semantic)
- git & govulncheck subprocesses use CommandContext with timeouts and bounded
  io.LimitReader output to prevent hangs and OOM
- MCP HTTP server sets ReadHeaderTimeout (+Read/Write/Idle) against Slowloris
- recover() around each check section so one panic can't abort the whole scan
- fix close/cancel leaks (triage body, http_post close, mcp ctx cancel;
  httpretry now drains before close); GitHub comment client threads ctx and a
  client timeout
- cap cache/config reads; tighten dir/file perms to 0750/0600

Tooling & quality:
- wire golangci-lint (16-linter v2 config) into CI, non-blocking for now
- %w error wrapping; sha1 -> sha256 for cache/identity keys; remove dead code,
  redundant conversions, and loop-var copies; prealloc where bounded
- justified //nolint:gosec on intentional subprocess/file/jitter sites
  (production gosec findings now zero)

Build, vet, gofmt clean; 430 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilder, CLI cleanup

Behavior-preserving refactors (output byte-for-byte identical, 430 tests pass):

- runner/checks: replace the hardcoded section-dispatch if-ladder with a
  data-driven sectionRegistry ([]sectionDef) iterated by Build; all 8 sections
  covered, panic-recovery (safeRun) and ordering preserved (new registry.go)
- checks/quality: add warnFinding() builder and migrate 21 hand-written
  FindingInput struct literals onto it, incl. the three per-language
  performance scanners and the three near-identical AI-quality emitters
- cli: extract parseFlags helper and name exit-code constants (exitOK/exitError)
  in new exit.go; migrate the five commands.go handlers

Note: the per-language performance scanners were found to use genuinely
different strategies (Go AST / Python indent / TS brace-depth), so no shared
line-scanner was forced.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hing

Lock in the H1/H2 hardening:
- ValidateBaseRef rejects option-injection refs (leading '-') and
  out-of-charset values; accepts legitimate refs and the stdin sentinel
- MatchPattern returns false (never panics) on malformed globs, matches
  valid glob semantics, and is idempotent across the sync.Map cache

476 tests pass (was 430).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- fix remaining errcheck, govet-shadow, gocritic, unparam findings
- scoped //nolint:contextcheck on the git helpers that use a contained
  timeout (deeper ctx threading tracked as a follow-up)
- //nolint:prealloc on the genuinely unbounded finding accumulators
- exclude revive's documentation/naming style rules (exported-doc,
  package-comments, unused-parameter); the correctness/safety linters gate CI.
  Documenting the public pkg/codeguard API is a tracked follow-up
- exclude test fixtures from gosec/errcheck/prealloc/bodyclose
- remove continue-on-error from the CI golangci-lint step: lint is now
  blocking. golangci-lint run reports 0 issues.

Build/vet/gofmt clean; 476 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…forcement

Note the data-driven sectionRegistry + safeRun panic recovery, the base_ref/
git subprocess hardening contract (and why the contextcheck nolints are
deliberate), and that golangci-lint is now CI-blocking with revive style rules
excluded.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The config is golangci-lint v2 schema, but action@v6 installs v1.64.8, which
rejects it ("additional properties 'version'/'settings' not allowed"). Bump to
@v8 (installs golangci-lint v2, runs on Node 24) and pin version v2.12.2 to
match local. `golangci-lint config verify` passes; run reports 0 issues.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alxxjohn alxxjohn merged commit 0d79c40 into main Jul 1, 2026
13 checks passed
@alxxjohn alxxjohn deleted the harden-perf branch July 1, 2026 01:25
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