Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ jobs:
# what makes it worth having: a scanner that lists every advisory
# touching the module graph produces noise, and noise gets switched off.
# Measured before switching it on, 2026-08-02: no vulnerabilities found.
run: go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 ./...
run: go run golang.org/x/vuln/cmd/govulncheck@v1.7.0 -tags "$(cat .github/build-tags)" ./...

staticcheck:
name: staticcheck
Expand Down Expand Up @@ -382,7 +382,7 @@ jobs:
# both of them the word "Pillow" at the start of an error string, which
# is the name of the library that refused the image rather than a
# sentence. Zero findings with the config in place.
run: go run honnef.co/go/tools/cmd/staticcheck@v0.8.1 ./...
run: go run honnef.co/go/tools/cmd/staticcheck@v0.8.1 -tags "$(cat .github/build-tags)" ./...

lint:
name: linters
Expand Down Expand Up @@ -426,7 +426,7 @@ jobs:
# .golangci.yml. Measured before switching this on: zero findings from
# ineffassign and one from misspell, which turned out to be a comment
# written in the wrong language rather than a typo.
run: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 run ./...
run: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.2 run --build-tags "$(cat .github/build-tags)" ./...

semgrep:
name: semgrep
Expand Down Expand Up @@ -538,7 +538,7 @@ jobs:
# versions read from the build. Not from a scan: measured 2026-08-27,
# syft reading the window binary names every module with an exact
# version and attaches a licence to one of thirty.
run: go run ./internal/legal/cmd/sbom -seed "${GITHUB_SHA}" -o ours.spdx.json
run: go run -tags "$(cat .github/build-tags)" ./internal/legal/cmd/sbom -seed "${GITHUB_SHA}" -o ours.spdx.json

- name: scan the binaries
# Pinned by commit like every other action here. The scan is evidence,
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
# cannot drift from what a manifest will record.
run: |
set -euo pipefail
version="$(go run ./cmd/tfg version)"
version="$(go run -tags "$(cat .github/build-tags)" ./cmd/tfg version)"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "the code says $version"

Expand Down Expand Up @@ -380,7 +380,7 @@ jobs:
# window archives and the command line ones, because a dot sorts
# before an underscore. With it, the four files a person uses to check
# a download sit together at the end, under the things they check.
go run ./internal/legal/cmd/sbom \
go run -tags "$(cat .github/build-tags)" ./internal/legal/cmd/sbom \
-seed "${GITHUB_REF_NAME}" \
-o "incoming/verify-tfg_${version}.spdx.json"
echo "SBOM=incoming/verify-tfg_${version}.spdx.json" >> "$GITHUB_ENV"
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ because it turns other people's test suites red.

### Security

- **Building the program yourself now needs the build tag, and says so if you
leave it out.** The AVIF encoder has an assembly path that reads past the end
of a buffer and takes the process down on some picture sizes. Every workflow
here has passed the tag that turns it off since the encoder arrived - and
none of the three ways `README.md` offered for building it yourself did, so
`go install`, a distribution package and a build from a checkout all got the
path this project describes as reading outside its buffer.

A build without the tag now stops at the compiler with a message naming it,
rather than producing a binary that works until it meets the wrong picture
size. Install with:

```
go install -tags noasm github.com/donislawdev/TestingFilesGenerator/cmd/tfg@latest
```

The files the program produces are the same either way, so nothing you have
generated changes.

- **A recipe that nests lists deeply is refused before it is read, whichever way
it is written.** The check that already refused deeply nested brackets counted
only brackets, and the same nesting written as `- - - - x` costs two bytes a
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,18 +169,24 @@ Linux has no equivalent to sign them with.
**With Go installed:**

```
go install github.com/donislawdev/TestingFilesGenerator/cmd/tfg@latest
go install -tags noasm github.com/donislawdev/TestingFilesGenerator/cmd/tfg@latest
```

**From source.** Needs Go 1.26.5 or newer, and nothing else:

```
git clone https://github.com/donislawdev/TestingFilesGenerator
cd TestingFilesGenerator
go build ./cmd/tfg
go build -tags "$(cat .github/build-tags)" ./cmd/tfg
```

The desktop window is a second binary, `go build ./cmd/tfg-gui`. It draws
**The tag is not optional.** The AVIF encoder has an assembly path that reads
past the end of a buffer and takes the process down on some picture sizes, and
the tag turns it off. Building without it does not compile, and says so. The
files it produces are the same either way.

The desktop window is a second binary,
`go build -tags "$(cat .github/build-tags)" ./cmd/tfg-gui`. It draws
through OpenGL and reaches it through C, so that one needs a C compiler and is
built natively on each system. Built without one it still compiles, and says on
start that it has no window in it and that everything is on the command line.
Expand Down
33 changes: 33 additions & 0 deletions internal/format/avif/requiretag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build !noasm

// Part of package avif. See avif.go.
package avif

// A build without the tag does not compile, on purpose.
//
// The package comment on avif.go has said since 2026-08-29 that the encoder's
// AVX2 path reads past the end of a buffer and takes the process down on some
// picture sizes - a 640x256 picture does it in two runs out of three. The tag
// that turns that path off lives in .github/build-tags, every workflow step
// passes it, and two guards keep those steps honest.
//
// None of that reaches somebody who builds this themselves. README.md offered
// three ways to do it and not one of them carried the tag, so "go install
// ...@latest", a distribution packager and a contributor running the program
// from a checkout all got the path this project describes as reading outside
// its buffer. Both binaries reach it: internal/format/all registers AVIF
// unconditionally, and go list -deps says cmd/tfg links it.
//
// Failing here is the honest place to fail. The alternative shapes were
// weighed on 2026-09-06 and turned down: registering AVIF as unavailable
// without the tag keeps go install working but makes "tfg formats" answer
// differently depending on how the binary was made, and inverting the tag so
// the assembly is opt-in is a larger change to CI and to the byte contract for
// no more safety than this. Owner's call.
//
// The message is written as a value rather than as a comment because the
// compiler prints it. Nothing here is reached at run time, and with the tag
// this file does not exist at all.
type buildWithoutTheNoasmTag struct{}

var _ buildWithoutTheNoasmTag = "this build is missing its build tags, and without them the AVIF encoder reads past the end of a buffer and takes the process down. Build with -tags \"$(cat .github/build-tags)\", or see the install section of README.md"
6 changes: 5 additions & 1 deletion internal/guard/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ func TestTheReleaseMakesItsDocumentAndHandsItOver(t *testing.T) {
all := script.String()

for what, want := range map[string]string{
"the bill of materials is generated": "go run ./internal/legal/cmd/sbom",
// The package rather than the whole command line. Build tags were added
// in front of it on 2026-09-06 and this guard asked for a literal that no
// longer existed, which is a guard describing the shape of a line rather
// than what the job has to do.
"the bill of materials is generated": "./internal/legal/cmd/sbom",
"it is written where the build is handed over": "-o \"incoming/verify-tfg_${version}.spdx.json\"",
"the statement travels with the build": "build.provenance.sigstore.json",
} {
Expand Down
93 changes: 90 additions & 3 deletions internal/guard/buildtags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"image"
"image/color"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
Expand All @@ -30,7 +31,11 @@ import (
// measured across the whole size ladder, so D11 is untouched. Encoding a
// 320x240 picture goes from about 6 ms to about 10 ms, which is still several
// times faster than the road this project turned down.
const buildTagsFile = "../../.github/build-tags"
const buildTagsFile = "../../" + buildTagsFileName

// buildTagsFileName is the same file as a person types it, which is what
// belongs in a message telling somebody what to do about it.
const buildTagsFileName = ".github/build-tags"

func buildTags() string {
raw, err := os.ReadFile(buildTagsFile)
Expand Down Expand Up @@ -96,7 +101,22 @@ func TestEveryWorkflowCommandThatBuildsUsPassesTheBuildTags(t *testing.T) {
}

// compilesOurCode says whether a workflow line runs the compiler over this
// module. Lines that fetch and run somebody else's tool are not ours to tag.
// module.
//
// It used to say that lines fetching and running somebody else's tool are not
// ours to tag, and that sentence hid three jobs. A tool fetched with "go run"
// and then pointed at ./... loads and type checks our packages exactly as the
// compiler does, and the tag selects different files inside two of our
// dependencies - so govulncheck was doing its reachability analysis through a
// build we do not ship, and staticcheck and golangci-lint were reading files no
// released binary contains and not reading the ones it does. Found by an
// outside review on 2026-09-05 and confirmed here.
//
// So "go run" is two commands wearing one name. Running a tool to read a
// configuration file - "golangci-lint config verify" - never opens a package
// and needs no tag. Running one over our packages does, and so does running one
// of our own commands. Both of those say so by naming a package pattern of
// ours, which is what the last line asks.
func compilesOurCode(line string) bool {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "#") {
Expand All @@ -107,7 +127,74 @@ func compilesOurCode(line string) bool {
return true
}
}
return false
return strings.Contains(trimmed, "go run") && strings.Contains(trimmed, "./")
}

// A build without the tags does not compile, and says which tags.
//
// Everything above keeps the tag on the commands this project runs. None of it
// reaches somebody who builds the program themselves, and until 2026-09-06
// README.md offered three ways to do that and not one carried the tag. So
// "go install ...@latest", a distribution packager and a contributor running
// from a checkout all got the AVX2 path that reads past the end of a buffer -
// while every workflow here was careful not to.
//
// The refusal is at build time on purpose. The alternative is a binary that
// works until it meets a picture size that takes the process down, which is the
// same defect arriving months later and somewhere nobody can act on it.
//
// Asked of the compiler rather than of the source, because what matters is that
// the build FAILS. A guard reading internal/format/avif for a build constraint
// would stay green against a file that had stopped failing.
func TestABuildWithoutTheBuildTagsRefusesAndSaysWhy(t *testing.T) {
out := filepath.Join(t.TempDir(), "untagged.bin")
cmd := exec.Command("go", "build", "-o", out, "./cmd/tfg")
cmd.Dir = filepath.Join("..", "..")
combined, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("cmd/tfg built with no build tags at all.\n" +
"Without them the AVIF encoder's AVX2 path reads past the end of a buffer and takes " +
"the process down on some picture sizes, so a build that gets that far is a build " +
"somebody will run. internal/format/avif is where the refusal lives.")
}
said := string(combined)
if !strings.Contains(said, "build tags") {
t.Errorf("the build failed without the tags, which is right, but what it said does not "+
"name build tags:\n%s\nSomebody meeting this has to be able to act on it.", said)
}
}

// The install instructions carry the tags the build needs.
//
// One file names the tags and everything else reads it, which works for the
// commands this project runs and cannot work for a command somebody types
// before they have the repository. So README.md carries the tag itself, and
// this is what keeps that copy honest.
func TestTheInstallInstructionsCarryTheBuildTags(t *testing.T) {
tags := buildTags()
raw, err := os.ReadFile(filepath.Join("..", "..", "README.md"))
if err != nil {
t.Fatalf("reading README.md: %v", err)
}

checked := 0
for i, line := range strings.Split(string(raw), "\n") {
if !strings.Contains(line, "go install") && !strings.Contains(line, "go build ") {
continue
}
checked++
if strings.Contains(line, buildTagsFileName) || strings.Contains(line, "-tags "+tags) {
continue
}
t.Errorf("README.md line %d tells somebody to build without the build tags:\n %s\n"+
"What to do: add -tags %q to it, or read them from %s where the command is run "+
"inside a checkout. Without them the AVIF encoder reads past the end of a buffer.",
i+1, strings.TrimSpace(line), tags, buildTagsFileName)
}
if checked == 0 {
t.Fatal("no line of README.md was recognised as an install or build command, so this " +
"guard checked nothing")
}
}

// The size that crashed, encoded here so a build that lost the tag says so
Expand Down
5 changes: 4 additions & 1 deletion internal/guard/licencelist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ func buildCommandLine(t *testing.T) string {
if os.Getenv("GOOS") == "windows" || filepath.Separator == '\\' {
binary += ".exe"
}
build := exec.Command("go", "build", "-trimpath", "-o", binary, "../../cmd/tfg")
// With the tags. Without them this build stopped compiling on 2026-09-06,
// and the failure arrived as a SKIP rather than as a red guard - which is
// the silent shape this project has paid for before.
build := exec.Command("go", "build", "-tags", buildTags(), "-trimpath", "-o", binary, "../../cmd/tfg")
build.Env = append(os.Environ(), "CGO_ENABLED=0")
if out, err := build.CombinedOutput(); err != nil {
t.Skipf("building the command line binary is not possible here: %v\n%s", err, out)
Expand Down
5 changes: 4 additions & 1 deletion internal/guard/lintconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ func TestTheWorkflowRunsTheLinterAndVerifiesItsConfiguration(t *testing.T) {
// workflow that happens to end the same way.
runs := false
for _, line := range strings.Split(text, "\n") {
if strings.Contains(line, tool+"@") && strings.Contains(line, "run ./...") {
// "run" and the package pattern rather than the two side by side. Build
// tags landed between them on 2026-09-06, and a guard that reads a line
// as a literal string reports the flag as a missing job.
if strings.Contains(line, tool+"@") && strings.Contains(line, " run ") && strings.Contains(line, "./...") {
runs = true
}
}
Expand Down
5 changes: 4 additions & 1 deletion internal/guard/sbomgate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ func TestTheWorkflowScansBothBinariesAndReadsTheReport(t *testing.T) {
for _, want := range []string{
"-trimpath -o dist/tfg ./cmd/tfg",
"-trimpath -o dist/tfg-gui ./cmd/tfg-gui",
"go run ./internal/legal/cmd/sbom",
// The package rather than the whole line: build tags went in front of it
// on 2026-09-06, and a guard reading a literal reports a flag as a missing
// job.
"./internal/legal/cmd/sbom",
"python .github/scripts/sbom_gate.py scan.json ours.spdx.json",
} {
if !strings.Contains(workflow, want) {
Expand Down
5 changes: 4 additions & 1 deletion internal/guard/windowsubsystem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ func TestTheWindowBinaryStartsWithoutAConsole(t *testing.T) {

built := filepath.Join(t.TempDir(), binary.name)

args := []string{"build"}
// With the tags, because a build without them does not compile at all
// since 2026-09-06 - and because the header this reads belongs to the
// binary this project ships rather than to some other build of it.
args := []string{"build", "-tags", buildTags()}
if binary.ldflags != "" {
args = append(args, "-ldflags="+binary.ldflags)
}
Expand Down
Loading