Skip to content

fix(guest-image): allowlist layer entry types and fixture-test the unpack (RIG-3850) - #1334

Open
rigel-mintaka wants to merge 5 commits into
mainfrom
compass-managed/rig-3850-layer-allowlist
Open

rigel-mintaka wants to merge 5 commits into
mainfrom
compass-managed/rig-3850-layer-allowlist

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Closes two of the three deferred findings on guest rootfs assembly.

  • Header allowlist. Before extracting each OCI layer, assemble-layers.sh rejects any entry that is not a regular file, directory, symlink or hardlink. It also rejects a hardlink that is absolute or has a .. component, a symlink to an absolute path outside /nix/store, and a relative symlink that climbs above the archive root. Across all 120 pinned layers the real image ships 18,367 files, 4,241 directories, 2,954 symlinks (790 absolute, all under /nix/store), no escaping relative symlinks, and no hardlinks, so the rule takes nothing away.
  • Shared script. The per-layer unpack, the directory-mode restore and the userland-contract resolver move out of the derivation's inline shell into guest-image/assemble-layers.sh, so the rootfs build and its tests run the same code. The published rootfs is byte-identical before and after: sha256 eca6ee5ec8b17ae5… on both builds.
  • Fixtures. compass-guest-assembly-tests builds tiny deterministic layers in the sandbox. It covers whiteout-then-re-add, an opaque directory with same-layer siblings, a layer replacing a symlink parent with a directory, the contract resolving through an in-image /nix/store link, a missing contract binary, a FIFO, an escaping symlink, a host-path symlink and an escaping hardlink. Each guard was red-checked: disabling it made its fixture fail. It runs as guest-image:test, and ci depends on it.

The redirect finding stays open. curl's --location drops the Authorization header on the cross-host 307 to pkg-containers.githubusercontent.com (verified with curl -v), and the bearer is GHCR's anonymous constant, so there is no credential to leak.

Spec-impact: none. Refs RIG-3850

Co-authored-by: Matt Wilkinson matt@rigel.build

…pack (RIG-3850)

Closes two of the three deferred findings on guest rootfs assembly.

- **Header allowlist.** Before extracting each OCI layer, `assemble-layers.sh` rejects any entry that is not a regular file, directory, symlink or hardlink. It also rejects a hardlink that is absolute or has a `..` component, a symlink to an absolute path outside `/nix/store`, and a relative symlink that climbs above the archive root. Across all 120 pinned layers the real image ships 18,367 files, 4,241 directories, 2,954 symlinks (790 absolute, all under `/nix/store`), no escaping relative symlinks, and no hardlinks, so the rule takes nothing away.
- **Shared script.** The per-layer unpack, the directory-mode restore and the userland-contract resolver move out of the derivation's inline shell into `guest-image/assemble-layers.sh`, so the rootfs build and its tests run the same code. The published rootfs is byte-identical before and after: `sha256 eca6ee5ec8b17ae5…` on both builds.
- **Fixtures.** `compass-guest-assembly-tests` builds tiny deterministic layers in the sandbox. It covers whiteout-then-re-add, an opaque directory with same-layer siblings, a layer replacing a symlink parent with a directory, the contract resolving through an in-image `/nix/store` link, a missing contract binary, a FIFO, an escaping symlink, a host-path symlink and an escaping hardlink. Each guard was red-checked: disabling it made its fixture fail. It runs as `guest-image:test`, and `ci` depends on it.

The redirect finding stays open. curl's `--location` drops the `Authorization` header on the cross-host 307 to `pkg-containers.githubusercontent.com` (verified with `curl -v`), and the bearer is GHCR's anonymous constant, so there is no credential to leak.

Spec-impact: none. Refs RIG-3850

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@trunk-io

trunk-io Bot commented Sep 25, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@linear-code

linear-code Bot commented Sep 25, 2026

Copy link
Copy Markdown

RIG-3850

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-managed-rig-3850-lay.compass-eng-docs.pages.dev

Deployed from compass-managed/rig-3850-layer-allowlist at 83fdfd6.

rigel-mintaka and others added 3 commits September 25, 2026 18:51
…cape (RIG-3850)

Review findings on the layer allowlist:

- A member name containing " link to " or " -> " moved the parsed link
  target, so an absolute hardlink could pass. The header listing now
  escapes spaces in names, which leaves the raw delimiter unambiguous.
  It also uses -P so absolute targets are listed as published.
- Directory-mode restore took the last whitespace field as the name. It
  now keeps the full escaped name, which tar -T unquotes.
- The absolute-hardlink fixture was built but never run. It now runs,
  alongside the new delimiter-in-name and spaced-directory fixtures.
- The script header says why this is bash.

Removing -P, the space escaping, or the full-name fix each fails a
fixture. The rootfs erofs is still byte-identical (eca6ee5e).

Spec-impact: none. Refs RIG-3850

Co-authored-by: Matt Wilkinson <matt@rigel.build>
restore-dir-modes feeds directory names to tar -T, which reads a line
starting with "-" as an option. A layer directory named "--directory=.."
passed every existing check and retargeted the mode restore outside the
rootfs. check_headers now rejects any member whose name starts with "-".
Fixture e8 fails without the check. The rootfs erofs is still
byte-identical (eca6ee5e).

Spec-impact: none. Refs RIG-3850

Co-authored-by: Matt Wilkinson <matt@rigel.build>
Both header parsers stripped the five-field prefix with a greedy space
match, so a leading space in a name vanished. " --directory=.." then
reached tar -T as an option, and any directory named with a leading
space lost its restored mode. The strip now consumes exactly the single
space after the time field. check_headers also rejects names starting
with an escaped space.

Fixtures e9 (leading-space option name) and f2 (leading-space dir mode)
fail without the respective fix. The rootfs erofs is still
byte-identical (eca6ee5e).

Spec-impact: none. Refs RIG-3850

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka marked this pull request as ready for review September 26, 2026 01:27
A formatter pass reflowed compass-guest-initrd, which this change does
not touch. Restore the original layout so the diff stays on the unpack.

Spec-impact: none. Refs RIG-3850

Co-authored-by: Matt Wilkinson <matt@rigel.build>
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