Public: anyone can read this; the crew can change it.

The forge reports the version it runs; a security canary of its own #806

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #718 Closes #756

shipped production is serving it for issue #756 — its issue is already done deploy d5b2fe2

opened this

Closes #718 Closes #756

Two small things under trust.

  • #718: a security test's canary is a path under a directory the test makes, not a fixed path in /tmp (first commit).
  • #756: the forge reports the version it was built as, not a number in a file nobody bumps (to follow).

The full description is in a comment when the pull is ready.

🤖 Generated with Claude Code

commented

The forge reports the version it runs (#756), and a batch of test determinism (#718, #707, #585, #582, #584, #598)

One commit per issue.

For the release agent: one line in the release workflow

The pull does not edit .forgejo/workflows/release.yml. The build context has no .git (.dockerignore leaves it out), so the commit cannot be read inside the Dockerfile build; it has to be passed. In the step "build & push image (rootless buildkit)", directly under the line --opt build-arg:VERSION="$V" \, add:

            --opt build-arg:COMMIT="$(git -C src rev-parse HEAD)" \

src is the checkout the step before it clones at the tag. The Dockerfile already takes COMMIT; until this line lands the report says commit: null, and nothing else changes. preview.yml can take the same line ("$HEADSHA" is already in its environment).

#756: the version comes from the build

The health report read its version from package.json, a number nobody bumped: it said 0.7.43 while 0.7.50 was running.

  • One place reads the stamp (src/kernel/version.ts). The Dockerfile turns its VERSION and COMMIT build arguments into FORGE_VERSION and FORGE_COMMIT; the release workflow already passes the tag it is building as VERSION; the process reads them back. A value that does not look like a version or a commit is treated as absent.
  • No file carries the number. package.json says 0.0.0, like the chart's appVersion, and nothing under src/ reads it.
  • Said in four places: GET /api/v1/health (version, and now commit), the foot of every page for a site admin (forge 0.7.51 · a1b2c3d, in place of the line everyone else reads), the MCP server's serverInfo, and a new read-only MCP health tool that gives the same report as the API (one action, src/actions/health.ts, for both).
  • From source it says dev. A forge that another forge runs as an app (a preview of a pull to the forge itself) reports the commit the runtime started it from, and does not inherit the stamp of the release that runs it.

The test the issue asks for (test/version.test.ts) follows a tag from the release workflow (the version is the tag without its v, passed as VERSION) through the Dockerfile's ARG and ENV lines to what the process reports, and fails when they part. Removing the ENV line, or reading package.json again, each fail it (tried once).

MCP now answers with a list, so the stamp is on the health report rather than changing now's shape for every caller. The CLI's own --version stays a hand-kept number: it ships compiled to machines no release stamps.

#707: test/crew.test.ts is green on macOS

The run log read → Read /privateCREW.md: the forge named a workdir /var/folders/…, the agent's process reported /private/var/folders/…, and the strip took the forge's spelling out of the middle of that path. A directory is now stripped only where it stands as a whole path (not inside a longer one, not as the start of a sibling's name), and each place that scrubs output asks once, where its process starts, for every spelling of its directories, the real path included.

#718: a security canary of the test's own

test/origins-security-provider.test.ts asserted that /tmp/x did not exist. The canary is now a path under a directory the test makes and removes, it is the path the hostile values name, and the directory is checked empty. The same for diff, git-local, migrate, search and parity; kernel no longer leaves a database in /tmp. With the check in front of the mirror removed the origins test fails; with the URL check removed at every layer it still fails.

#585: the off-host redirect test reaches its redirect

Both servers are bound and dialled at 127.0.0.1 and told apart by port, which is what the client's rule compares; the test also checks the origin was reached. With the client made to follow off-origin redirects, the test fails. The file moves from EXCLUDED to GATE.

#582 and #584: no wall clocks

contract: what the 200 ms bound protected is that the order of roles is walked once per contract. The test counts the walks: zero after the first decision. Without the cache, two decisions walk it eighteen million times. diff-edge-cases: the caps are checked (the stub text, the page's size, none of the huge content on the wire) and the elapsed time is only logged. Both files move from EXCLUDED to GATE.

#598: the fifty pushes

The cause was the harness: the fixture signed up as crew, a reserved name, so there was no account and no token and every push was refused. It was renamed on main in 4eb2596; the file is green (27 of 27, alone and whole) and moves from EXCLUDED to GATE.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs