This guide describes GoClaw's vulnerability assessment lifecycle, how govulncheck is used for call-graph analysis, and how to consume OpenVEX statements to eliminate false-positive security alerts.
Traditional software security tools rely on naive package version matching. If an application imports a library with a known CVE, the scanner reports a vulnerability even if the vulnerable function is never invoked or compiled into the binary.
GoClaw solves this problem using a 3-pillar supply-chain security architecture:
govulncheck analyzes symbol call graphs against the official Go vulnerability database (vuln.go.dev).
| Feature | Naive Scanner (Trivy / Grype basic) | govulncheck (GoClaw standard) |
|---|---|---|
| Analysis Level | Module name & version string only | AST, Symbol, and Call-Graph reachability |
| False Positive Rate | High (reports CVEs in unused functions) | Near Zero (only flags executed symbols) |
| Runtime Impact | None | None (runs at CI/build time only) |
govulncheck is automatically executed during the release build pipeline. You can also run it locally:
The release pipeline pins the scanner version rather than tracking @latest, so that a
report can be reproduced later from the same inputs. Use the same pin locally (it is
GOVULNCHECK in .github/workflows/release.yml; v1.1.4, the earlier pin, crashes on Go 1.27):
# Run human-readable vulnerability inspection
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 ./...
# Export machine-readable JSON for compliance archives
go run golang.org/x/vuln/cmd/govulncheck@v1.8.0 -json ./... > govulncheck-report.json
In its default text mode, govulncheck exits 3 when it finds reachable
vulnerabilities and 0 when it does not; any other non-zero exit is a tool or build
failure, and the report it produced should not be trusted. With -json it exits 0
whether or not it finds anything, so read the findings from the report itself, not from
the exit code.
When an enterprise security scanner audits GoClaw's SBOM, it may flag transitive library CVEs. Instead of lengthy email exchanges and manual review meetings, GoClaw publishes an official OpenVEX file (goclaw-${VERSION}-vex.json) alongside each release.
VEX lets compliance scanners filter out non-exploitable findings using the fixed set of justifications defined by the OpenVEX specification. The definitions below are the specification's, not GoClaw's — a VEX that reinterprets them is a VEX an auditor can reject:
component_not_present: The vulnerable component is not in the product at all.vulnerable_code_not_present: The component is present, but the vulnerable code was removed or never shipped.vulnerable_code_not_in_execute_path: The vulnerable code is present but is never invoked.vulnerable_code_cannot_be_controlled_by_adversary: The vulnerable code executes, but an attacker cannot influence the input that reaches it.inline_mitigations_already_exist: The product contains a built-in protection that prevents exploitation, which the attacker cannot subvert and the operator cannot disable.[!IMPORTANT] A statement is only worth publishing if it survives being checked against the
govulncheckreport shipped beside it. Where a vulnerability is reachable and a fixed upstream release exists, GoClaw upgrades the dependency instead of writing a justification for it.
security/vex.json){
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://goclaw.ulab.mn/vex/release-advisory",
"author": "ULAB.mn Security Team <security@ulab.mn>",
"timestamp": "2026-09-21T00:00:00Z",
"version": 2,
"statements": [
{
"vulnerability": {
"name": "GO-2026-6452",
"description": "Panic via negative shared-string index in github.com/xuri/excelize"
},
"products": [
"pkg:golang/ulab.mn/agents/goclaw"
],
"status": "not_affected",
"justification": "inline_mitigations_already_exist",
"impact_statement": "The vulnerable code is reachable ... the panic is recovered in WorkerPool.executeTask (internal/agent/worker_pool.go), so the residual impact is the failure of the single spreadsheet call that triggered it."
}
]
}
The audit assets of the current release are hosted under
https://goclaw.ulab.mn/releases/${VERSION}/, where VERSION is the version named in
https://goclaw.ulab.mn/releases/latest.json (for example v0.0.96). Only the current
release is served there: a request for an earlier version's files returns 404. The
files of earlier releases are available from security@ulab.mn.
https://goclaw.ulab.mn/releases/${VERSION}/goclaw-${VERSION}-linux-amd64-sbom.cdx.json (and …-sbom.spdx.json)https://goclaw.ulab.mn/releases/${VERSION}/goclaw-${VERSION}-vex.jsonhttps://goclaw.ulab.mn/releases/${VERSION}/goclaw-${VERSION}-govulncheck.json (JSON) and goclaw-${VERSION}-govulncheck.txt (text summary)https://goclaw.ulab.mn/releases/${VERSION}/checksums.jsonVERSION=$(curl -fsSL https://goclaw.ulab.mn/releases/latest.json \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["version"])')
# Scan SBOM with VEX suppression in a single command
grype "sbom:goclaw-${VERSION}-linux-amd64-sbom.cdx.json" --vex "goclaw-${VERSION}-vex.json"
trivy sbom "goclaw-${VERSION}-linux-amd64-sbom.cdx.json" --vex "goclaw-${VERSION}-vex.json"
With the VEX advisory supplied, a scanner suppresses the findings the advisory accounts for. It will not necessarily report a clean result: the scanner's own database may carry advisories GoClaw has not yet assessed, and suppression depends on the scanner matching the VEX product identifier to the SBOM. Treat the VEX as evidence to review alongside the govulncheck report, not as a guarantee of a green scan.