Skip to content

ci: harden CI matrix, static analysis, supply-chain, and example/doc gates#194

Open
dgenio wants to merge 6 commits into
mainfrom
claude/issue-triage-grouping-wpehf4
Open

ci: harden CI matrix, static analysis, supply-chain, and example/doc gates#194
dgenio wants to merge 6 commits into
mainfrom
claude/issue-triage-grouping-wpehf4

Conversation

@dgenio

@dgenio dgenio commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary

Implements a coherent group of CI / repository-automation issues in one PR.
They all center on .github/ (with supporting Makefile targets, scripts, and
docs), so the shared ci.yml is restructured once instead of conflicting
across seven separate PRs.

Related issues

Fixes #109
Fixes #110
Fixes #165
Fixes #177
Fixes #181
Fixes #187
Fixes #188

What changed

.github/workflows/ci.yml — restructured:

Static analysis (#110)

  • .golangci.yml with a pragmatic linter set; make lint/make golangci/make sec.
  • Two real fixes: set ReadHeaderTimeout on the HTTP proxy and metrics
    servers (gosec G112).
  • Intentional gosec findings annotated inline with // #nosec <rule> -- <reason>
    (operator-specified file reads, the proxy launching the operator's MCP server,
    forwarding to the configured upstream, non-secret 0o644 files) — no global
    rule disables, so new findings still fail.

Supply-chain (#177).github/dependabot.yml (gomod + github-actions,
grouped), a govulncheck job + make vuln, and a SECURITY.md posture section.

Examples (#181)scripts/check-examples.sh + make examples run every
bundled example (validate / policy test / check / audit verify+summarize+export
/ the demo) through the binary. Hermetic.

Docs (#165)scripts/check-doc-claims.py + make doc-check verify every
agentfence <subcommand> in the docs is a real command and that local doc
links resolve, with a --selftest that proves the checks fire.

PR triage (#188)actions/labeler (.github/labeler.yml, existing
labels only) auto-labels PRs by changed path; an advisory check flags
non-Conventional-Commit titles. Documented in CONTRIBUTING.md.

How verified

Locally (Go 1.24, golangci-lint v2.5.0, gosec, govulncheck):

  • gofmt -l — clean
  • go build ./... — ok
  • golangci-lint run ./...0 issues
  • gosec -quiet ./... — exit 0 (all findings annotated)
  • go test -race ./... — all packages pass
  • make examples — all examples validated end-to-end
  • make doc-check11 files: commands + links resolve; --selftest PASS
  • All new/edited YAML (ci.yml, pull-request-triage.yml, dependabot.yml,
    labeler.yml, .golangci.yml) parse cleanly.

Could not run locally: govulncheck (the sandbox blocks vuln.go.dev);
it will run in the new security CI job, which has network access.

Risks / notes

  • The cross-OS matrix may surface a genuine platform-specific test failure
    (its purpose) — that would be a real finding to address, not workflow noise.
  • golangci-lint-action@v8 is pinned to golangci-lint v2.5.0 (matches the
    config schema used locally).
  • gosec/govulncheck install via go install @latest in CI so scanners use
    current rules/advisories; Dependabot keeps Actions pinned.

Checklist

  • Tests added or updated — new example/doc validation gates; existing race
    suite green
  • Documentation updated if needed — CONTRIBUTING.md, SECURITY.md
  • CI passes — verified locally; govulncheck runs in CI
  • Issue number included

🤖 Generated with Claude Code

https://claude.ai/code/session_01QSZXR9R2Ejmw5FreniZ5yM


Generated by Claude Code

…gates

Bundles a coherent set of CI and repository-automation issues that all live in
.github/ (plus their Makefile/scripts/doc support) so the shared ci.yml is
restructured once instead of conflicting across separate PRs.

- #187: trigger CI once per change (push scoped to main; pull_request for
  branches) and add a concurrency group that cancels superseded runs.
- #109: run the test suite on a Linux/macOS/Windows matrix with the race
  detector; coverage stays single-source on Linux. Windows now exercises the
  engine's path-safety logic.
- #110: add golangci-lint (.golangci.yml) and gosec gates + `make lint`/`make
  golangci`/`make sec`. Intentional gosec findings (operator-specified file
  reads, the proxy launching the operator's MCP server, forwarding to the
  configured upstream, non-secret 0o644 files) are annotated inline with
  `// #nosec <rule> -- <reason>`; fixed the two real G112 findings by setting
  ReadHeaderTimeout on the HTTP servers.
- #177: add Dependabot for gomod + github-actions and a govulncheck CI job
  (`make vuln`); documented the supply-chain posture in SECURITY.md.
- #181: add scripts/check-examples.sh + `make examples` and a CI job that runs
  every bundled example through the binary so examples/ cannot drift.
- #165: add scripts/check-doc-claims.py + `make doc-check` and a CI job that
  verifies documented commands exist and internal doc links resolve (with a
  self-test that proves the checks fire).
- #188: auto-label PRs by changed path via actions/labeler (.github/labeler.yml,
  existing labels only) and flag non-Conventional-Commit titles (advisory).

CONTRIBUTING.md documents the new targets and auto-labeling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSZXR9R2Ejmw5FreniZ5yM
Copilot AI review requested due to automatic review settings June 24, 2026 12:15
First CI run surfaced three environment/portability failures (not logic):

- lint: golangci-lint v2.5.0 (built with Go 1.25) panics type-checking
  packages loaded by `go-version: stable` (1.26) — "file requires newer Go
  version go1.26". Pin the lint job to Go 1.24.
- test (macos-latest): Go 1.22's linker omits the macOS LC_UUID load command,
  so `-race` test binaries abort under the current macos-latest dyld
  ("missing LC_UUID load command" / "signal: abort trap"). Bump the test
  matrix to Go 1.24, which builds run cleanly on all three runners.
- test (windows-latest): TestAuditKeygenCreatesUsableKeys asserted the private
  key file mode is 0600, but Windows has no POSIX permission bits (os.Stat
  reports 0666). Skip the mode assertion on Windows; keygen still requests
  0600 and the key-load check still runs everywhere.

ubuntu test, security (gosec+govulncheck), examples, docs, goreleaser-check,
and docker-build all passed on the first run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSZXR9R2Ejmw5FreniZ5yM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates several CI and repository-automation improvements centered around .github/, adding cross-platform test coverage, new static-analysis/security gates, and automated checks to prevent example/docs drift—plus supporting Makefile targets, scripts, and documentation updates.

Changes:

  • Restructures CI to avoid duplicate runs, add concurrency cancellation, and run tests on a Linux/macOS/Windows matrix (with single-source coverage from Linux).
  • Adds dedicated CI jobs for linting, security scanning (gosec + govulncheck), examples validation, and docs claim/link validation.
  • Adds Dependabot configuration and PR triage automation (path-based auto-labeling + advisory Conventional Commit title warning), plus targeted gosec-related code hardening/annotations.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
SECURITY.md Documents supply-chain posture (Dependabot, scanners, signing/SBOM references).
scripts/check-examples.sh New hermetic script to exercise bundled examples end-to-end against the built binary.
scripts/check-doc-claims.py New script to verify documented CLI commands exist and relative doc links resolve (with selftest).
Makefile Adds lint/security/vuln/examples/doc-check targets and tool overrides.
internal/proxy/proxy.go Adds inline gosec suppression for intentionally operator-specified command execution.
internal/policy/policy.go Adds inline gosec suppression for operator-specified file reads.
internal/metrics/metrics.go Adds inline gosec suppression for an intentional/guarded integer conversion case.
internal/httpproxy/httpproxy.go Hardens HTTP server with ReadHeaderTimeout and annotates intentional proxy forwarding findings.
internal/audit/signing.go Adds inline gosec suppressions for operator-supplied key paths and intentional public-key perms.
CONTRIBUTING.md Updates contributor guidance for new CI gates, local targets, and PR triage behavior.
cmd/agentfence/main.go Adds ReadHeaderTimeout for metrics server; adjusts Close defers and annotates intentional file perms/reads.
.golangci.yml Introduces golangci-lint configuration (pragmatic linter set + errcheck exceptions).
.github/workflows/pull-request-triage.yml Adds PR auto-labeling via labeler + advisory Conventional Commit title check.
.github/workflows/ci.yml Restructures CI triggers/concurrency; adds matrix test + new lint/security/examples/docs jobs.
.github/labeler.yml Maps changed paths to existing repo labels for auto-labeling.
.github/dependabot.yml Enables grouped weekly updates for Go modules and GitHub Actions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CONTRIBUTING.md Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread scripts/check-doc-claims.py Outdated
claude added 4 commits June 24, 2026 12:23
- CONTRIBUTING: the lint-tools install snippet was labeled golangci-lint but
  only installed gosec; add the golangci-lint installer and label both.
- CONTRIBUTING: correct the pre-push paragraph — the cross-platform `test` job
  runs `go test -race` directly (coverage on Linux) and `fmt-check` runs in the
  `lint` job, rather than `make ci` verbatim.
- ci.yml: pin the security job to Go 1.24 instead of 'stable' so a new Go
  release can't break it unannounced (go.mod's 1.22 floor is too old to
  `go install` the latest gosec/govulncheck). The lint job was already pinned.
- check-doc-claims.py: write the selftest fixture via a context manager so the
  handle is flushed/closed before the checks read it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSZXR9R2Ejmw5FreniZ5yM
…are real

Pinning the security job to Go 1.24 (from review feedback) made govulncheck
fail with 9 standard-library CVEs present in the go1.24.13 toolchain but already
fixed upstream (e.g. os/net/url, "Fixed in: go1.25.8"). govulncheck reports
stdlib vulnerabilities against the toolchain it runs on, so it must use the
latest stable Go — otherwise it flags the pinned release's own unpatched stdlib.
Revert the security job to 'stable' with a comment explaining why it differs
from the (correctly pinned) lint job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSZXR9R2Ejmw5FreniZ5yM
…per job

Audit follow-up on PR #194.

- internal/httpproxy/httpproxy.go: the two upstream-request calls were
  annotated `// #nosec G704`, but gosec has no G7xx rules, so the
  suppression matched nothing. The rule that applies to an HTTP request
  built from a variable URL is G107; annotate the NewRequestWithContext
  call with G107 and drop the annotation on client.Do (which raises no
  gosec rule). This preserves the "annotate, never globally disable"
  invariant so a genuinely new finding still fails the build.
- .github/workflows/pull-request-triage.yml: move `pull-requests: write`
  from the workflow level to the `label` job only. The advisory `title`
  job merely emits a ::warning and needs no write scope; under
  pull_request_target, keeping the elevated token scoped to the one job
  that needs it is least-privilege.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEcWEtCkPD8z9xwFPZjkwL
Follow-up to acd90ed. gosec (v2.22+/latest, used by the security CI job) does
have a G704 rule — "SSRF via taint analysis" — and raises it on both
upstream-request calls in the HTTP proxy. acd90ed reannotated one call as
`#nosec G107` and dropped the annotation on client.Do on the assumption that
"gosec has no G7xx rules", so neither call was suppressed and the security job
failed with 2 G704 findings.

Annotate both the NewRequestWithContext and client.Do calls with
`#nosec G107 G704` so the intentional operator-configured proxy forwarding is
suppressed regardless of which rule ID the installed gosec version emits, while
still failing on any genuinely new finding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QSZXR9R2Ejmw5FreniZ5yM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment