Report privately through GitHub, at Security advisories. That opens a channel only the maintainers can read.
Please do not open a public issue for a vulnerability.
Expect a first response within 3 working days, and an assessment within 10. If a fix is warranted, the advisory is published together with the release that carries it, with credit unless you ask otherwise.
The CLI, the extraction allowlist, the redaction path, and the release pipeline.
The single thing worth attacking here is the boundary between a Terraform plan file and anything that leaves the machine, so anything that widens that boundary is the highest-value report you can send:
- an attribute reaching the payload that is not on the allowlist
- a resource address surviving into the payload unhashed
- finding prose, which is rebuilt server side and never uploaded, appearing in the payload
--dry-runprinting something different from what an upload would send
That last one is the load-bearing claim. --dry-run is the only channel through
which a user can audit what would travel, so a divergence between it and the real
upload is a vulnerability even if nothing sensitive escapes in the specific case.
- A Terraform plan file that already contains a secret. Plan files can hold sensitive values, which is why this tool reads by allowlist. The plan itself is the user's to protect.
- Findings that are numerically wrong. Those are bugs, and serious ones, but they go in the public issue tracker.
- Denial of service by feeding the CLI a deliberately enormous plan.
Every push, every pull request, and once a week on a schedule:
| Check | What it is for |
|---|---|
govulncheck |
Go advisories, at symbol level: it reports a CVE only when a code path actually reaches the vulnerable function, so a graph match with no reachable call does not become noise |
CodeQL, security-extended |
static analysis of this repository's own Go code |
| OpenSSF Scorecard | supply-chain posture of the repository itself, published so it can be checked by somebody who does not work here |
| Dependency review | blocks a pull request that would introduce a dependency with a known advisory |
| Dependabot | Go modules and GitHub Actions, weekly |
Results land in the repository's Security tab. govulncheck also runs again
inside the release workflow, before anything is published, so a release cannot
ship past an advisory that CI would have caught.
One, gopkg.in/yaml.v3, which has no dependencies of its own. Everything else is
the Go standard library.
That is a deliberate constraint rather than an accident of scope. This binary runs inside customer environments, and every transitive dependency is supply-chain surface that somebody has to defend during a security review.
Every release ships checksums.txt, a Sigstore signature over it, and an SPDX
SBOM per archive.
# 1. the archive matches the published checksum
sha256sum -c checksums.txt --ignore-missing
# 2. the checksum file itself came from this repository's release workflow
cosign verify-blob \
--bundle checksums.txt.bundle \
--certificate-identity-regexp 'https://github.com/headroom-project/headroom/.github/workflows/release.yml@refs/tags/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
# 3. the binary was built by that workflow, from that commit
gh attestation verify headroom_linux_amd64.tar.gz --repo headroom-project/headroomThe signing is keyless. There is no private key held by a person, so there is no private key for a person to lose: the certificate binds the signature to the workflow identity, and step 2 is what checks that binding.