Skip to content

Cowork image build 5 prep: #140 security-review lows, verify defaults - #141

Merged
Winterrks merged 3 commits into
cowork/output-integrationfrom
cowork/image-build5-prep
Oct 24, 2026
Merged

Winterrks merged 3 commits into
cowork/output-integrationfrom
cowork/image-build5-prep

Conversation

@Winterrks

Copy link
Copy Markdown
Member

Folds the lows from the #140 security review and checks that the image build workflow at this head builds what integration holds. The workflow stays unarmed (REVIEWED_HEAD zeros, trigger branch cowork/guest-image-build-5 unchanged).

  • dataroots.c: make_plan_dir reads the mode back from the held descriptor and refuses a mismatch; test-build fchmod interposer + ratchet mutant.
  • The epoch scan's leading-zero skip and digit check are pinned by a test and three ratchet mutants.
  • verify.py: require_environment defaults True; a stray /etc/gitconfig in the cpio is refused.
  • Pins: SDK stays ec91fe06 (no guest-relevant change in agent-sdk eea959b5); init/relay/bootstrap source digests already match the tree (the production programs do not compile dataroots.c or shares.c).

Build-5 caveat: the guest init/relay BINARY digests are checked hard by image.py build_guest (ValueError, not exit 3). See the PR comment.

…criptor, the epoch scan's skips pinned by tests, verify requires the environment claim by default and refuses a stray /etc/gitconfig

- dataroots.c make_plan_dir: after fchmod the bits are read back from the held descriptor and a mismatch is refused (as make_one does); a test build interposes fchmod to lie, and a ratchet mutant removes the check.
- The epoch scan's leading-zero skip and the full-name digit check (i == n) are the lax side; a test now holds both (junk and non-canonical sibling names never make an epoch stale) and three ratchet mutants cover them. The two existing read-back mutants follow the new text.
- verify.py: require_environment defaults to True in the library (callers that checked an older root pass False explicitly); a cpio that carries /etc/gitconfig of its own is refused, claim or no claim (the image ships the file only under /opt/winter/environment/base).
- Guest source digests (init, relay, bootstrap helper) and the SDK pin ec91fe06 are already current; nothing moves.
@Winterrks

Copy link
Copy Markdown
Member Author

What build 5 would do at this head.

UNPINNED (expected, image.py exit 3, published, run turns red in the assert step): expectedArtifact (null), ownerLibrary.binarySha256 and launcher/executable digests changed by the SDK move (via check_expected), packageLockSha256 only if the lock differs.

NOT expected: build_guest raises ValueError (not exit 3) when the freshly built init or winter-guest-relay binary differs from guest.programs.*.binarySha256 (dede7fd9..., eed3a8f2..., last set in dc995df). The production sources compiled since then changed (wmx.c, trusted_config.c, boot.c, init_linux.c, relay.c); if the binaries changed, build 5 dies before publishing anything, and verify.py (pinned_init from pins.json) would refuse too. These two digests come from a local build, not from the workflow: build.py --zig <pinned 0.14.1> --update-pins (test_build_digests_are_checked_against_a_real_build_when_zig_is_provided with WINTER_PINNED_ZIG checks it). No Zig is available to this lane (and a downloaded one may not be run), so it was not done here. Either run that before arming, or make build_guest/verify tolerate a binary mismatch as UNPINNED; say which.

… (UNPINNED), a set pin that differs stays a hard failure

- pins.json: guest.programs.init and winter-guest-relay binarySha256 are null (sourceSha256 unchanged): no Zig is run by a lane, so build 5 reports the digests it produces.
- image.py: build_guest keeps refusing a set pin that differs and now refuses a missing key; guest_binary_problems reports each null with the built digest and the build ends at exit 3 with the other unpinned fields. check_startup uses the built init when the pin is null.
- verify.py: a null init pin is read back only with allow_unpinned, against the init digest the root's own receipt claims (cpio and receipt must agree), and reports initPinned false; without the flag it is refused, a missing key is an error.
- Tests for null, mismatch and missing key; test_binary_pin_mutants.py (11 mutants, "treat a mismatch as unpinned" among them).
@Winterrks

Copy link
Copy Markdown
Member Author

Update at c0975e0: the init and relay binary pins are now an explicit null (UNPINNED, exit 3, the built digest reported); a set pin that differs is still a hard failure and a missing key is an error. Supersedes the caveat in my earlier comment. The workflow uploads a 14-day workflow artifact on exit 3 by design (never a release) and then turns the run red.

…n guards held for empty and wrongly typed pins, relay read-back, digests.json carries both guest digests

- dataroots.c make_one reads the leaf's mode back from the descriptor (02770, setgid included) and refuses a mismatch; the test fchmod can lie on the root's own mode ("leaf") or add the sticky bit ("special"), which also kills the 07777 -> 0777 mask mutant; the plan-directory comment is now true.
- image.py / verify.py: an empty or wrongly typed binary pin ('', 0, false, a list) is a hard failure, never unpinned (tests; mutants for truthiness and `not pinned_init`). verify.py also holds the relay to its pin (null only with allow_unpinned, read back against the receipt; a set pin must be the relay in the cpio, 0555, and the receipt must say so).
- Comments: the top-level etc is a symlink into the base, so a literal etc/gitconfig cpio entry would write through it.
- image_build_report.py: digests.json carries relaySha256 and initPinned / relayPinned beside initSha256, and problems() names an unpinned or stale guest binary pin.
@Winterrks
Winterrks marked this pull request as ready for review October 24, 2026 22:15
@Winterrks
Winterrks merged commit 8f38426 into cowork/output-integration Oct 24, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant