#1686

DEP-91 (2/5): gate hoopagent vulnerabilities and add govulncheck

Openracerxdlwants to merge intofrom

Stack (DEP-91)

Split from #1662 for reviewability, then rescoped: main has since landed #1681 (dependency/toolchain CVEs) and #1693 (hoophq/hoopagent), which superseded two PRs of the original stack.

  • #1683 — Go dependency + toolchain CVE bumps — closed, superseded by #1681

  • #1685 — opt-in hoophq/hoopdev-minimal — closed, superseded by #1693

  • #1684 — 1. agent-tools: drop kernel-header CVEs, modernize clean train

  • #1686 — 2. Vulnerability gates for hoopagent + govulncheck ← you are here

  • #1687 — 3. SBOM + bundled-tool manifest on releases

  • #1688 — 4. Weekly base-OS rebuild of hoopagent

  • #1689 — 5. Image release integrity hardening

Merge in order, bottom-up. Each PR targets the one below it.

The remaining work is what main still lacks: the fat image's kernel-header CVEs, and any vulnerability gate, SBOM, or scheduled OS patching for hoophq/hoopagent.


What

Adds the vulnerability gating hoophq/hoopagent currently has none of:

  • Build smoke check for Dockerfile.agent, both flavours, both platforms. It was previously only built after a tag push, so a broken Dockerfile could reach main and fail post-merge.
  • Enforcing Trivy gate on fixable CRITICAL/HIGH for both flavours.
  • Unprivileged-user assertion — resolves USER to a real uid, handling distroless (10001:10001, numeric) and minimal (hoop, by name) correctly.
  • Report-only Trivy on agent-tools, which bundles upstream CLIs whose CVEs cannot be rebuilt away.
  • govulncheck (pinned v1.6.0) on every PR for the shipped Go binary.

Why

hoophq/hoopagent is the low-CVE image, but nothing enforced that. main runs Trivy with --scanners license only — there is no vulnerability gate anywhere, and no source-level Go gate.

Risk

Adds blocking checks. The fat image stays report-only, so existing releases are not newly gated.

Note on the placeholder binary

The release tarballs do not exist at PR time, so the smoke build stages placeholder tars named hoop_*_Linux_${TARGETARCH}.tar.gz — matching Dockerfile.agent's glob, which uses GOARCH values, not uname -m. This gates Dockerfile correctness and the base layer, not a functional agent.

How to test

Open any PR touching Dockerfile.agent: Build Slim Agent Image runs for both flavours and must pass. Go Vulnerability Gate runs on every PR.

Part of DEP-91.

Automated by MisterMal

Activity

8
qodo-code-review[bot]2mo ago

PR Summary by Qodo

DEP-91: add Trivy gates for minimal image and govulncheck PR gate

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

AI Description

• Add enforcing Trivy scan to block fixable HIGH/CRITICAL CVEs on minimal image PR builds.
• Add govulncheck job gating reachable Go vulnerabilities for the shipped hoop binary.
• Add non-blocking Trivy reporting for agent-tools images to track fixable CVE trends.
Diagram

graph TD
  A["PR / commit"] --> B["GitHub Actions"] --> C(["Go Vulnerability Gate"]) --> D["Go source (client)"]
  B --> E(["Build hoopdev-minimal"]) --> F(["Trivy gate (HIGH/CRITICAL)"]) --> G["Dockerfile.minimal"]
  B --> H(["Trivy report-only (agent-tools)"])
High-Level Assessment

The following are alternative approaches to this PR:

1. Use official GitHub Actions for scanners
  • ➕ Avoids Docker-in-Docker socket mounting for Trivy runs
  • ➕ Built-in features for caching and standardized outputs
  • ➕ Easier future SARIF/code-scanning integration
  • ➖ May reduce transparency versus explicit CLI flags in the workflow
  • ➖ Action upgrades can introduce behavior changes unless pinned carefully
2. Publish SARIF/Artifacts instead of only Step Summary
  • ➕ Enables trend tracking and triage in GitHub Security/Code Scanning UI
  • ➕ Preserves full scan output for auditing and debugging
  • ➖ More CI configuration and retention management
  • ➖ May add noise unless tuned (especially for report-only agent-tools)

Recommendation: Current approach is sound for DEP-91’s goals: it pins tool versions for deterministic gating (govulncheck v1.6.0, trivy image scanning) and intentionally keeps agent-tools non-blocking to avoid ignore-list sprawl. Consider a follow-up to emit SARIF/artifacts and/or move to pinned official actions if reducing docker-sock exposure becomes a priority.

Files changed (4) +110 / -1

Other (4) +110 / -1
agent-tools-build.ymlAdd report-only Trivy scan summary for agent-tools images (amd64/arm64) +44/-0

Add report-only Trivy scan summary for agent-tools images (amd64/arm64)

• Runs Trivy against built agent-tools images in both architecture jobs, capturing fixable HIGH/CRITICAL counts. Writes a short top-packages summary to the GitHub step summary without failing the workflow (explicitly report-only).

.github/workflows/agent-tools-build.yml

minimal-image-build.ymlEnforce Trivy HIGH/CRITICAL gate on minimal PR-smoke image +15/-0

Enforce Trivy HIGH/CRITICAL gate on minimal PR-smoke image

• Adds a blocking Trivy scan step for the minimal PR-smoke image, failing the job if any fixable HIGH/CRITICAL vulnerabilities are present. Documents that the scan is intended to enforce the OS/base layer for the minimal image.

.github/workflows/minimal-image-build.yml

pullrequest.ymlAdd govulncheck job to gate reachable Go vulnerabilities on PRs +42/-1

Add govulncheck job to gate reachable Go vulnerabilities on PRs

• Introduces a new PR job that checks out both hoop and libhoop, installs a pinned govulncheck version, and runs it against the shipped client module. This makes reachable Go dependency/toolchain vulnerabilities a merge blocker.

.github/workflows/pullrequest.yml

Dockerfile.minimalApply security upgrades during minimal image build via apt-get upgrade +9/-0

Apply security upgrades during minimal image build via apt-get upgrade

• Adds an apt-get upgrade step before package installation to pull current upstream security patches. Explicitly documents the reproducibility trade-off (non-bit-identical rebuilds over time) as intentional for a low-CVE base image.

Dockerfile.minimal

qodo-code-review[bot]2mo ago

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 🔗 Cross-repo conflicts (2) 📜 Skill insights (0)


1. Trivy report can still fail 🐞 Bug ☼ Reliability
Description
In agent-tools-build.yml, the new “non-blocking” Trivy report step ignores Trivy failures ("||
true") but then runs an unguarded jq ... | head pipeline; if Trivy produces empty/invalid JSON,
jq can fail and block the workflow anyway. This can prevent later steps (including publish) from
running even though the step is intended to be report-only.
Code

.github/workflows/agent-tools-build.yml[R197-200]

+            echo "Strict gate is on hoophq/hoopdev-minimal. Top packages:"
+            echo '```'
+            jq -r '[.Results[]?.Vulnerabilities // []]|add|group_by(.PkgName)|map({p:.[0].PkgName,c:length})|sort_by(-.c)|.[]|"\(.c)\t\(.p)"' trivy-vuln.json 2>/dev/null | head -20
+            echo '```'
Relevance

●●● Strong

Non-blocking step should never fail; guarding jq/head is small deterministic workflow-hardening.

PR-#1595 PR-#1619

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The step intentionally ignores Trivy failures when writing trivy-vuln.json, but then immediately
runs a jq pipeline (without any fallback) to render the "Top packages" list; this can still fail
if the JSON file is empty/invalid. The same pattern is duplicated in the arm64 job.

.github/workflows/agent-tools-build.yml[179-201] .github/workflows/agent-tools-build.yml[300-318]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Trivy step is explicitly labeled/report-only and uses `|| true` to avoid failing when Trivy errors, but the subsequent `jq ... | head` command is not guarded. If `trivy-vuln.json` is empty or invalid (e.g., Trivy fails before producing valid JSON), `jq` may exit non-zero and the step can still fail.

## Issue Context
This occurs in both amd64 and arm64 jobs in `agent-tools-build.yml`.

## Fix Focus Areas
- .github/workflows/agent-tools-build.yml[185-201]
- .github/workflows/agent-tools-build.yml[302-318]

## Suggested fix approach
- Make the "Top packages" rendering resilient, e.g.:
 - Wrap it in a conditional JSON validity check (`jq -e . trivy-vuln.json >/dev/null 2>&1`) and print a fallback message when invalid; and/or
 - Append `|| true` to the `jq ... | head` pipeline.
- Also consider making the count query robust to empty `.Results` by using `add // []` (or similar) so it produces `0` instead of `null` when appropriate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Gate needs libhoop token 🔗 Cross-repo conflict ☼ Reliability
Description
The new blocking govulncheck job requires checking out hoophq/libhoop using secrets.GH_TOKEN; if
that secret is not available in a given PR context, the checkout/build will fail and block merges.
This adds/strengthens a cross-repo access contract between this repo’s PR CI and the libhoop repo’s
availability/permissions.
Code

.github/workflows/pullrequest.yml[R328-331]

+          repository: hoophq/libhoop
+          path: "./libhoop"
+          token: ${{ secrets.GH_TOKEN }}
+          ref: ${{ needs.resolve-libhoop.outputs.sha }}
Relevance

●●● Strong

They’ve accepted adding explicit checks around required secrets to avoid late workflow failures.

PR-#1595

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The newly-added govulncheck job checks out hoophq/libhoop using secrets.GH_TOKEN, and the
workspace includes ./libhoop, meaning the job’s success depends on successfully fetching that
separate repository/module. This is an explicit cross-repo auth/availability dependency introduced
into the PR merge gate.

.github/workflows/pullrequest.yml[3-4] .github/workflows/pullrequest.yml[27-41] .github/workflows/pullrequest.yml[309-331] go.work[1-16] External repo: hoophq/libhoop, go.mod [1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`govulncheck` is now a blocking PR job and it checks out `hoophq/libhoop` using `secrets.GH_TOKEN`. If `GH_TOKEN` is missing/unavailable for a PR run (common in fork PR scenarios), the gate will fail before analysis.

### Issue Context
The workflow uses `on: pull_request`, and both `resolve-libhoop` and `govulncheck` rely on `secrets.GH_TOKEN` to access libhoop.

### Fix Focus Areas
- .github/workflows/pullrequest.yml[3-4]
- .github/workflows/pullrequest.yml[27-41]
- .github/workflows/pullrequest.yml[309-331]
- go.work[1-16]

### Suggested change
Implement one of the following patterns:
1) **Conditional gate**: only run `govulncheck` (or only the libhoop checkout step) when `secrets.GH_TOKEN` is present, and otherwise skip the job with a clear message.
2) **OSS stub fallback**: when libhoop checkout cannot run, create `./libhoop` by symlinking to `./_libhoop` (or running the existing mapping target) so govulncheck can still compile against the OSS stub.
3) If `hoophq/libhoop` is intended to be publicly readable for CI, remove the hard dependency on `secrets.GH_TOKEN` for checkout and use a standard token/unauthenticated checkout.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Gate tracks libhoop head 🔗 Cross-repo conflict ☼ Reliability
Description
The new blocking govulncheck job compiles against the libhoop revision resolved at workflow runtime
(default-branch HEAD when no paired branch), so re-running the same Hoop PR commit can start failing
due to new changes/vulns in hoophq/libhoop. This increases cross-repo coupling and reduces CI
reproducibility across reruns.
Code

.github/workflows/pullrequest.yml[R328-331]

+          repository: hoophq/libhoop
+          path: "./libhoop"
+          token: ${{ secrets.GH_TOKEN }}
+          ref: ${{ needs.resolve-libhoop.outputs.sha }}
Relevance

●● Moderate

Reproducibility vs tracking latest libhoop is a policy trade-off; no close pinning precedent found.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR workflow’s resolve-libhoop job explicitly falls back to the current default-branch
checkout’s HEAD when there is no paired branch, and the newly-added govulncheck job checks out
libhoop at that resolved SHA. The workspace expects a local ./libhoop module, and libhoop is an
independent Go module in a separate repository, so its moving default-branch state can change the
dependency graph analyzed by govulncheck across reruns.

.github/workflows/pullrequest.yml[27-77] .github/workflows/pullrequest.yml[309-348] go.work[1-16] External repo: hoophq/libhoop, go.mod [1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new `govulncheck` merge gate checks out and compiles against `hoophq/libhoop` using a SHA that can resolve to the libhoop default branch HEAD when no paired branch exists. This makes Hoop PR results dependent on a moving external repo state and can cause the same PR commit to pass/fail depending on when the workflow is run.

### Issue Context
- `resolve-libhoop` falls back to the default-branch checkout’s current `HEAD` when a same-named paired branch does not exist.
- `govulncheck` then checks out that resolved SHA and compiles the workspace (which includes `./libhoop`).

### Fix Focus Areas
- .github/workflows/pullrequest.yml[27-77]
- .github/workflows/pullrequest.yml[309-348]
- go.work[1-16]

### Suggested change
- Introduce an immutable, Hoop-repo-controlled fallback libhoop revision for the “no paired branch” case (e.g., a committed `LIBHOOP_SHA` file or workflow variable), and have `resolve-libhoop` use that pinned SHA instead of “whatever HEAD is today”.
- Keep the paired-branch behavior as-is for coordinated cross-repo changes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Context
✅ Compliance rules (platform): 39 rules
✅ Cross-repo context
  Explored: repo: hoophq/libhoop (sha: 84d16e41)
  Not relevant to this PR: hoophq/infra
  Not relevant to this PR: hoophq/documentation
  Not relevant to this PR: hoophq/changelog
Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

qodo-code-review[bot]reviewed2mo ago
github-actions[bot]2mo ago

Migration Safety Analysis

No database migrations were changed in this PR. Safe to deploy to sandbox.

github-actions[bot]2mo ago

📋 API Changelog

API Changelog unknown vs. unknown

API Changes

GET /access-requests/rules

  • :warning: added the new jit_command enum value to the data/items/access_type response property for the response status 200
  • added the optional property data/items/skip_review_groups to the response with the 200 status

POST /access-requests/rules

  • :warning: added the new jit_command enum value to the access_type response property for the response status 201
  • added the new optional request property skip_review_groups
  • added the new jit_command enum value to the request property access_type
  • added the optional property skip_review_groups to the response with the 201 status

GET /access-requests/rules/{name}

  • :warning: added the new jit_command enum value to the access_type response property for the response status 200
  • added the optional property skip_review_groups to the response with the 200 status

PUT /access-requests/rules/{name}

  • :warning: added the new jit_command enum value to the access_type response property for the response status 200
  • added the new optional request property skip_review_groups
  • added the new jit_command enum value to the request property access_type
  • added the optional property skip_review_groups to the response with the 200 status

GET /connection-credentials

  • endpoint added
sandromello2mo ago

✅ Build Completed with Success, Version=1686.0.0-g27d00d4