From eb6871099f69d1ba650203bcd786c8d55bf887f2 Mon Sep 17 00:00:00 2001 From: scttbnsn <80784472+scttbnsn@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:18:05 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=A7=20chore(security):=20consolida?= =?UTF-8?q?te=20dep/CVE=20scanning=20on=20Grype=20+=20govulncheck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Portwing never carried Snyk on the repo side (no .snyk policy, no workflow step, no badge), so there's nothing to remove here. Snyk's SCM integration is being decommissioned org-wide because it scans the Go module requirement graph (go mod graph) rather than the compiled build graph and over-reports advisories in modules the binary never links in. This brings the existing Grype + govulncheck scan up to the sockguard pattern: - 🔄 Rename security-vuln-weekly.yml -> security-grype.yml; rename the source job to grype-deps (Go go.mod/go.sum + npm lockfiles in one pass). - ✨ Add a path-filtered pull_request trigger (source/deps/Dockerfile/the workflow itself); keep the weekly cron + workflow_dispatch. - 🔧 Guard the heavy container build off PRs (govulncheck + grype-deps carry fast PR coverage); grype-image stays on schedule/manual. - 🐛 Give each upload-sarif a distinct category (grype-image/grype-deps/gosec) so same-tool Grype SARIF uploads no longer clobber each other in code scanning. - 🔧 Pin govulncheck's Go toolchain to go.mod instead of a floating "1.26". - 📝 CHANGELOG: document the consolidation and the module-graph-vs-build-graph rationale under Unreleased -> Changed. Verified: actionlint + zizmor clean on the new workflow; grype scan of the git-archived tracked tree reports no vulnerabilities. --- ...ity-vuln-weekly.yml => security-grype.yml} | 62 +++++++++++++++---- CHANGELOG.md | 4 ++ 2 files changed, 53 insertions(+), 13 deletions(-) rename .github/workflows/{security-vuln-weekly.yml => security-grype.yml} (68%) diff --git a/.github/workflows/security-vuln-weekly.yml b/.github/workflows/security-grype.yml similarity index 68% rename from .github/workflows/security-vuln-weekly.yml rename to .github/workflows/security-grype.yml index f6ca387..ed7ed3f 100644 --- a/.github/workflows/security-vuln-weekly.yml +++ b/.github/workflows/security-grype.yml @@ -1,12 +1,27 @@ -name: "🛡️ Security: Vuln Scan" +name: "🛡️ Security: Grype & Govulncheck" run-name: >- ${{ - github.event_name == 'schedule' && '🛡️ Security: Vuln Scan — Weekly run' || - format('🛡️ Security: Vuln Scan — Manual by {0}', github.actor) + github.event_name == 'schedule' && '🛡️ Security: Grype & Govulncheck — Weekly run' || + github.event_name == 'pull_request' && format('🛡️ Security: Grype & Govulncheck — PR #{0}', github.event.pull_request.number) || + format('🛡️ Security: Grype & Govulncheck — Manual by {0}', github.actor) }} on: workflow_dispatch: + pull_request: + branches: [main, 'dev/*'] + # Dependency / source / build changes are what move the security + # surface — scope PR runs to those so doc-only PRs stay fast. + paths: + - '**/*.go' + - 'go.mod' + - 'go.sum' + - 'package.json' + - 'package-lock.json' + - '**/package.json' + - 'Dockerfile*' + - '.grype.yaml' + - '.github/workflows/security-grype.yml' schedule: - cron: '15 7 * * 1' # Weekly on Monday at 07:15 UTC @@ -17,7 +32,7 @@ env: GOVULNCHECK_VERSION: v1.2.0 concurrency: - group: security-weekly-${{ github.workflow }} + group: security-grype-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -39,7 +54,7 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version: "1.26" + go-version-file: go.mod - name: Install govulncheck run: go install "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}" @@ -52,13 +67,17 @@ jobs: run: | { echo "### Govulncheck" - echo "- Scanned Go dependencies for known vulnerabilities" + echo "- Reachability-based scan of Go dependencies (call-graph aware)" echo "- Tool version: ${GOVULNCHECK_VERSION}" echo "- Database: Go Vulnerability Database (vuln.go.dev)" } >> "$GITHUB_STEP_SUMMARY" grype-image: name: "🐳 Security: Grype Container Scan" + # Image build is the heavy step; keep it on scheduled/manual runs. + # PRs get fast, accurate dependency coverage from grype-deps + + # govulncheck without paying for a Docker build on every push. + if: github.event_name != 'pull_request' runs-on: ubuntu-latest timeout-minutes: 20 permissions: @@ -104,18 +123,19 @@ jobs: uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: sarif_file: ${{ steps.grype.outputs.sarif }} + category: grype-image - name: Summarize if: always() run: | { echo "### Grype Container Scan" - echo "- Scanned container image for HIGH and CRITICAL CVEs" + echo "- Scanned the built image (binary build-info → real shipped deps) for HIGH/CRITICAL CVEs" echo "- Base image: Wolfi (Chainguard) packages on FROM scratch" } >> "$GITHUB_STEP_SUMMARY" - grype-source: - name: "📦 Security: Grype Source Scan" + grype-deps: + name: "📦 Security: Grype Dependency Scan (Go + npm)" runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -132,9 +152,14 @@ jobs: with: persist-credentials: false - - name: Run Grype on source directory + - name: Run Grype across the repository (Go modules + npm lockfiles) + # Scanning the repo root catalogs go.mod/go.sum and package-lock.json, + # so a single pass covers both ecosystems. Grype matches the + # lockfile-resolved versions (not the full module requirement graph), + # so it does not emit the phantom-dependency findings a manifest-graph + # SCA reports for modules the binary never links in. uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 - id: grype-src + id: grype-deps with: path: . severity-cutoff: high @@ -142,11 +167,21 @@ jobs: output-format: sarif config: .grype.yaml - - name: Upload Grype source SARIF + - name: Upload Grype dependency SARIF if: always() && github.event.repository.visibility == 'public' uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: - sarif_file: ${{ steps.grype-src.outputs.sarif }} + sarif_file: ${{ steps.grype-deps.outputs.sarif }} + category: grype-deps + + - name: Summarize + if: always() + run: | + { + echo "### Grype Dependency Scan (Go + npm)" + echo "- Scanned Go modules and npm lockfiles across the repo for HIGH/CRITICAL CVEs" + echo "- Matches lockfile-resolved versions (no module-graph false positives)" + } >> "$GITHUB_STEP_SUMMARY" gosec: name: "🔐 Security: Gosec SAST" @@ -176,6 +211,7 @@ jobs: uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: sarif_file: gosec-results.sarif + category: gosec # NOTE: testssl job omitted — Portwing does not operate a TLS listener of its own # (TLS is handled by a reverse proxy in front). Re-add if Portwing gains direct diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6d8ae..b2d8a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Weekly soak test**: `quality-soak-weekly.yml` runs the agent (generic adapter) against a mock Docker upstream under a sustained loadgen mix — inventory/version/proxy reads plus SSE subscriber connect/hold/disconnect churn — and fails if its resident set grows past a configurable budget (64 MiB default) over a multi-hour soak. New harness under `benchmarks/cmd/{mockdocker,loadgen}` driven by `scripts/soak.sh`. Catches the long-lived-agent leak profile the unit/integration/fuzz tiers don't. - **Monthly benchmark tracking**: Go benchmarks on the per-request hot paths (auth middleware, Argon2id verify — cold derivation vs. warm SHA-256 cache, client-IP extraction, rate limiter) and the parse paths (PHC, image-ref, Drydock labels, trusted-proxy CIDRs, MCP dispatch). `quality-bench-monthly.yml` reruns them with `-benchmem -count=5` on the first of each month and retains the results for 90 days so a ns/op or allocs/op regression shows up month over month. Completes the test-posture parity with sockguard. +### Changed + +- **Standardized dependency/CVE scanning on Grype + govulncheck; Snyk stays off Portwing.** Snyk's GitHub SCM integration scans the full Go *module requirement graph* (`go mod graph`) instead of the compiled build graph, so it flags advisories in modules that transitive deps merely *require* but the binary never links in (nothing in `go list -deps ./...`, nothing reachable per govulncheck, clean under Grype). That's a methodology gap, not staleness, so it's being decommissioned org-wide. Portwing never wired Snyk into the repo (no `.snyk` policy, no workflow step, no README badge), so there was nothing to strip out on the repo side. govulncheck (Go call-graph reachability) and Grype (the built image's binary build-info, plus `go.mod`/`go.sum` and the npm lockfiles) already cover dependencies accurately. The existing weekly scan is consolidated into `security-grype.yml`, which now also runs on pull requests (path-filtered to source/deps/Dockerfile/the workflow itself), keeps the weekly cron and manual dispatch, guards the heavy container build off PRs (govulncheck plus the dependency scan give fast PR coverage), gives each scanner a distinct code-scanning `category` so the Grype image and dependency SARIF no longer clobber each other in the Security tab, and pins govulncheck's Go toolchain to `go.mod` instead of a floating `1.26`. + ## [0.3.0] - 2026-06-15 ### Added From e50465158c31d28bf2e56e00fea125b775894d28 Mon Sep 17 00:00:00 2001 From: scttbnsn <80784472+scttbnsn@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:33:11 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20fix(security):=20keep=20govu?= =?UTF-8?q?lncheck=20on=20latest=201.26.x=20and=20make=20gosec=20report-on?= =?UTF-8?q?ly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaced by running security-grype.yml on PRs: - 🐛 govulncheck: go-version-file resolved to go.mod's `go 1.26.0` (no toolchain directive), which reports stdlib advisories already fixed in 1.26.1+ (GO-2026-4599/4600/4601). Revert to go-version "1.26" to track the latest patch, matching the gating ci.yml scan and how the binary is built. - 🔧 gosec: add -no-fail so its heuristic findings (e.g. G115 int->uint in the mockdocker benchmark helper) feed the Security tab via SARIF without failing the run; CodeQL/Grype/govulncheck handle build gating. - 📝 CHANGELOG: correct the entry to drop the reverted go-version-file claim. --- .github/workflows/security-grype.yml | 13 +++++++++++-- CHANGELOG.md | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/security-grype.yml b/.github/workflows/security-grype.yml index ed7ed3f..bc8175c 100644 --- a/.github/workflows/security-grype.yml +++ b/.github/workflows/security-grype.yml @@ -54,7 +54,11 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: - go-version-file: go.mod + # go.mod declares only the language version (`go 1.26.0`) with no + # `toolchain` directive, so go-version-file would pin govulncheck to + # 1.26.0 and report stdlib advisories already fixed in 1.26.1+. Track + # the latest 1.26.x like the build and the gating ci.yml scan do. + go-version: "1.26" - name: Install govulncheck run: go install "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}" @@ -204,7 +208,12 @@ jobs: - name: Run Gosec uses: securego/gosec@9e6a9843d7a4a6e3e9a8539b02612c8a4aa3f889 # v2.27.1 with: - args: -fmt sarif -out gosec-results.sarif ./... + # Report-only: gosec is heuristic (e.g. G115 int->uint conversions in + # the benchmark helpers) and feeds findings to the Security tab via the + # SARIF upload below. Build gating is handled by CodeQL (ruleset-enforced), + # Grype, and govulncheck, so -no-fail keeps gosec from failing the run + # on advisory-level findings while still surfacing them for triage. + args: -no-fail -fmt sarif -out gosec-results.sarif ./... - name: Upload Gosec SARIF if: always() && github.event.repository.visibility == 'public' diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc5139..a91d1cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **Standardized dependency/CVE scanning on Grype + govulncheck; Snyk stays off Portwing.** Snyk's GitHub SCM integration scans the full Go *module requirement graph* (`go mod graph`) instead of the compiled build graph, so it flags advisories in modules that transitive deps merely *require* but the binary never links in (nothing in `go list -deps ./...`, nothing reachable per govulncheck, clean under Grype). That's a methodology gap, not staleness, so it's being decommissioned org-wide. Portwing never wired Snyk into the repo (no `.snyk` policy, no workflow step, no README badge), so there was nothing to strip out on the repo side. govulncheck (Go call-graph reachability) and Grype (the built image's binary build-info, plus `go.mod`/`go.sum` and the npm lockfiles) already cover dependencies accurately. The existing weekly scan is consolidated into `security-grype.yml`, which now also runs on pull requests (path-filtered to source/deps/Dockerfile/the workflow itself), keeps the weekly cron and manual dispatch, guards the heavy container build off PRs (govulncheck plus the dependency scan give fast PR coverage), gives each scanner a distinct code-scanning `category` so the Grype image and dependency SARIF no longer clobber each other in the Security tab, and pins govulncheck's Go toolchain to `go.mod` instead of a floating `1.26`. +- **Standardized dependency/CVE scanning on Grype + govulncheck; Snyk stays off Portwing.** Snyk's GitHub SCM integration scans the full Go *module requirement graph* (`go mod graph`) instead of the compiled build graph, so it flags advisories in modules that transitive deps merely *require* but the binary never links in (nothing in `go list -deps ./...`, nothing reachable per govulncheck, clean under Grype). That's a methodology gap, not staleness, so it's being decommissioned org-wide. Portwing never wired Snyk into the repo (no `.snyk` policy, no workflow step, no README badge), so there was nothing to strip out on the repo side. govulncheck (Go call-graph reachability) and Grype (the built image's binary build-info, plus `go.mod`/`go.sum` and the npm lockfiles) already cover dependencies accurately. The existing weekly scan is consolidated into `security-grype.yml`, which now also runs on pull requests (path-filtered to source/deps/Dockerfile/the workflow itself), keeps the weekly cron and manual dispatch, guards the heavy container build off PRs (govulncheck plus the dependency scan give fast PR coverage), gives each scanner a distinct code-scanning `category` so the Grype image and dependency SARIF no longer clobber each other in the Security tab, and runs gosec in report-only mode (`-no-fail`) so its heuristic findings still feed the Security tab without gating the build (CodeQL, Grype, and govulncheck handle the gating). ## [0.3.0] - 2026-06-15