Skip to content

GLIM clean-room Phase 3d r3 apt allowlist candidate

This is an additive, fixture-only candidate. It does not change the active r2 profile, selection, receipts, rival set, or production offline manifest. Its status is OPT_IN_NOT_READY, benchmark_eligible=false, and network_execution=NOT_RUN. No Docker, apt, resolver, download, dataset, bag, GT, scorer, or benchmark operation was executed.

Fixed precommit and resolver boundary

apt_package_requirements.schema.json accepts only versionless, explicit package names and architectures in sorted build/runtime lists. Its canonical SHA is the precommit identity. plan_glim_clean_room_r3_apt_allowlist.py emits a fixed, non-executing argv workflow for the pinned Jazzy image ros@sha256:31daab66eef9139933379fb67159449944f4e2dcf2e22c2d12cc715f29873e0f (linux/amd64, pull never). Build and runtime receive distinct fresh apt archive/cache roots; each role has its own apt-get --print-uris and download-only phases. The plan has no arbitrary shell fragments and records PROPOSED_REVIEW_REQUIRED, benchmark_eligible=false, and PROVISIONING_ONLY_NOT_RUN. The plan also carries an explicit host_executor.status=IMPLEMENTED_NOT_RUNTIME_VALIDATED marker bound to the executor source hash. Its host ... entries remain an auditable future- workflow contract; the executor is opt-in and cannot be used without an independent host authorization, so no runtime command is selected implicitly.

The planned host sequence is explicit and ordered: inspect the image, reject pre-existing output/container names, create and lstat a fresh 0700 output directory, run the pinned image with that directory as the only writable bind and all requirements/composer/schema inputs as read-only binds, then run apt-get update, base dpkg-query, apt/Release and rosdep snapshots, and separate build/runtime --print-uris ... install <names> followed by download-only install <names> phases. The container is disconnected, inspected, stopped, removed, and absence-checked before host-only staging and composition phases. Host phases use absolute paths below the fresh output root; /workspace/capture/... appears only in Docker argv. The plan records both mount identities and a versioned outer-receipt schema for command, stdout/stderr, raw snapshot, cleanup, and network-phase binding. Each resolver phase also seals its exact inner argv to a host-side JSON path; the host composer CLI consumes that argv plus the captured raw log rather than accepting an arbitrary shell command.

The resolver input is the strict JSON equivalent of a captured apt-get --print-uris result. Every URI line must have exactly four fields: shell-quoted HTTPS URL, safe .deb filename, positive decimal byte count, and either SHA256:<64 lowercase hex> or the apt-2.x-compatible MD5Sum:<32 lowercase hex> locator. The URL's percent-decoded basename must equal the filename. An MD5 value is never an authenticity root: the source snapshot must include the corresponding signed Packages representation and decoded plain index, whose Release-bound path/URL/SHA, Filename, Size and SHA256 are checked against the downloaded deb bytes. A URI SHA256, when present, must also equal that index SHA256. Unknown digest labels, duplicates, traversal, ambiguous query or fragment URLs, shell fragments, missing transitive packages, and nonzero resolver status fail closed. A resolver package record binds the URI to dpkg-deb Package/Version/Architecture, Essential/Pre-Depends/Multi-Arch, repository URL, Release/InRelease path/SHA, and the preinstall base-status SHA. A package already present in the base status is explicitly marked base_installed and has no acquired URI/deb; it is excluded from the new allowlist/ledger. Same name+architecture with different versions across roles is rejected; distinct explicit architectures remain distinct identities. Pre-Depends must resolve in the role or base set.

The signed Packages representation policy is fixed to the ordered candidates .xz, .gz, then plain Packages. The first available candidate must have both its compressed (or plain) path and its decoded Packages path/bytes bound by signed Release SHA-256/size entries. APT's local .lz4 cache, .bz2, unsigned or stale cache, and any external-codec or network fallback are rejected. For compressed candidates, bounded streaming standard-library decoding enforces 64 MiB compressed and decoded limits, a 256:1 expansion limit, mandatory EOF, and rejection of truncation, decoder errors, trailing bytes, and concatenated streams. These artifact limits are independent of the separate 16 GiB bound for signed Release numeric size declarations. That declaration-only parser bound accepts canonical ASCII unsigned decimal text through 17179869184; compressed, decoded, downloaded, and .deb limits do not change.

Host composer and review gate

apt_allowlist_composer.py is filesystem-only. Its seal_base_status_snapshot and build_resolver_output helpers consume the host-captured raw dpkg-query/--print-uris logs, downloaded deb roots, and source snapshot; they never run apt, dpkg, Docker, or a network client. The resolver documents are therefore runtime-generated inputs, not synthetic prebuilt inputs accepted by the host composer. It reopens the base status, both resolver documents, the apt/Release/rosdep source snapshot, and separate build/runtime downloaded-deb roots using no-follow, single-link, bounded reads. It computes each deb SHA/size and invokes only an injected fixture dpkg-deb metadata reader; production execution is intentionally absent from this non-executing candidate. A future executor must provide the fixed metadata command and bind its stdout/exit record in the outer receipt. Release and Packages bytes must match the repository ledger. The composer strictly parses the Release/InRelease SHA256: section and requires exactly one entry whose path (derived from the Packages URL) has the same Size and SHA256 as the captured Packages bytes; duplicate, missing, or URL-ambiguous entries fail closed. This is a Release metadata binding, not an OpenPGP signature verification. Signature validation and its outer receipt remain a future host/custodian gate and were not run. Missing/extra/symlinked/ hard-linked debs, URI/deb/control/SHA/size drift, source drift, base mutation, resolver self-hash drift, mixed role closure, or a non-fresh output root is a hard failure. The output contains a canonical collector-compatible package-allowlist.json and apt-acquisition-ledger.json, plus the complete role/base/resolver/source binding, but remains a PROPOSED_REVIEW_REQUIRED proposal.

The proposal is not collector input. materialize_reviewed requires a separate REVIEWED_FOR_COLLECTOR review document with a non-empty custodian signature/key identity bound to the exact proposal SHA and a referenced per-repository signature receipt index. The strong host receipt gate still rejects the current fixture-only executor outputs, so no collector input can be materialized. No production manifest is edited automatically. Candidate tool/schema hashes are separately bound in apt_allowlist_candidate.json; validate_glim_clean_room_r3_apt_allowlist.py rejects pending or drifted hashes.

OpenPGP signature candidate (NOT_RUN)

apt_release_signature.py and plan_glim_clean_room_r3_apt_signature.py add an additive, non-promoting signature contract. A future host must precommit a no-follow keyring byte artifact, its primary fingerprint/key ID, signing-subkey fingerprints, creation/expiry/revocation metadata, a fixed verification timestamp, and the exact gpg binary identity. The fixture validator rejects unknown, bad, expired, revoked, or otherwise untrusted status records; gpgv exit status alone is explicitly insufficient because it does not establish expiry or revocation state. The planned verifier uses --no-default-keyring, disables automatic key retrieval/import/location and autostart, emits status-fd data, and separately inventories the explicit keyring with --with-colons --with-fingerprint --with-subkey-fingerprint --show-keys. The VALIDSIG primary-key fingerprint is checked at its documented optional field after the signature class, while GOODSIG may name an explicitly precommitted signing subkey.

The receipt reopens and binds the status log, key-inventory log, exact argv, tool identity, fixed-time key metadata, detached Release.gpg bytes when applicable, and the existing Release SHA256 → Packages → downloaded-deb binding identity. Its ordered phases include trust/release reopen, signature and inventory verification, binding reopen, sealing, and cleanup. The additive apt_release_signature_executor.py now provides a bounded host executor candidate: it requires a full source-revalidated plan, an absolute precommitted binary path/SHA/version, fresh 0700 output and GnuPG directories, shell=False, a fixed environment, stdin=DEVNULL, timeout/resource limits, exclusive bounded logs, inventory-before-signature ordering, and no-default- keyring/no-auto-key/no-agent flags. It reopens every source and command projection before validating the sealed result, rejects self-rehashed or swapped output roots, and records cleanup identity/absence. Failed attempts are sealed as non-promoting results; cleanup or phase failures cannot become PASS. The default subprocess runner is deliberately disabled until an independent host authorization receipt exists; tests inject only a synthetic runner. Thus no keyring, GnuPG process, Docker, network, apt, or package acquisition was run. The executor is IMPLEMENTED_NOT_RUNTIME_VALIDATED, signature_runtime=NOT_RUN, and benchmark_eligible=false; an independently reviewed trust root and a host-owned runtime authorization/cleanup receipt are still required.

Host runtime authorization (NOT_READY)

apt_release_signature_authorization.py is the separate Ed25519 gate for the future SubprocessRunner path. It signs a domain-separated payload containing the exact candidate and fixed policy identities, plan canonical/file identity, executor source hash, trust-root/keyring and Release/Packages/signature/deb bindings, GnuPG binary path/SHA/version, root/homedir/output paths, a precommitted machine-identity artifact, validity interval, nonce, no-network/ no-shell policy, resource limits, and the complete expected workflow/argv. Only the policy path bound by apt_allowlist_candidate.json is accepted; caller-selected trust stores and self-declared public keys are rejected. The policy also binds the cryptography implementation file/SHA and exact backend version. Machine identity is a strict tagged union: a READY policy selects exactly one legacy v1 artifact or keyed PROVISIONED_ED25519 artifact and binds its method/schema/file/canonical/identity plus keyed public-key, challenge, and proof projections. Initial keyed authorization performs live validation; receipt reopen performs offline validation and preserves the sealed live-host evidence. The checked-in policy is NOT_READY with no authorized key and no machine artifact, so it cannot authorize a process. A keyed provisioning_receipt_sha256 is only an external receipt identity here, not a cryptographic verification of that receipt.

The executor obtains the host wall-clock time and verifies it is inside the signed authorization interval before creating the output root or starting a command. That actual execution time is sealed in both an atomic, persistent nonce claim and the receipt; reopening uses that sealed time, so a receipt that was valid before later expiry remains reproducible while an expired authorization is rejected before its first output. Output-root, parent identity, machine artifact, and all source/tool identities are reopened on receipt validation. runner=None and an explicit SubprocessRunner therefore fail closed without authorization; injected fake runners remain a synthetic, non-promoting path and cannot be paired with an authorization. A successful run is labelled HOST_AUTHORIZED_SUBPROCESS / RUNTIME_VERIFIED; a failed authorized attempt is labelled RUNTIME_FAILED, benchmark_eligible=false, and promotion=FORBIDDEN. Only the successful state is accepted by the per-repository promotion gate. No private key or signed PASS receipt is checked in, and no GPG/network/apt/Docker execution was performed.

Per-repository promotion binding (NOT_READY)

apt_signature_promotion.py is now the shared fail-closed boundary used by allowlist materialization and the collector's explicit promotion mode. A review must reference the new apt_signature_receipt_index.schema.json; the index contains exactly one receipt root, plan file/identity, input root, and complete deb binding for each repository (url, release_url) in the acquisition ledger. The validator rejects missing, duplicate, extra, and cross-repository replay entries, then reopens the executor's full plan/source/trust-root/Release/Packages/deb projection and receipt sidecars. Static validation, self-rehashed JSON, fixture/fake-runner output, INJECTED_FIXTURE_ONLY, NOT_RUNTIME, and promotion=FORBIDDEN are never promotion evidence.

materialize_reviewed cannot produce collector input without this index and a successful host-authorized receipt for every repository. seal_to_capture retains its review-only direct path for synthetic capture evidence; when collector_input_allowed=true is requested it requires the same index and gate, and fails closed otherwise. The current candidate has no authorized GnuPG execution, so this gate intentionally rejects all current receipts. Neither the active r2 profile/selection/receipts nor README claims are changed or promoted.

Evidence status

The focused synthetic suite exercises valid build/runtime composition and review-only materialization, URI quoting/percent encoding, MD5 locator plus Packages-SHA binding (and malformed digest rejection), duplicate URI rejection, missing transitive packages, Release/Packages and base-status drift, resolver status/self-hash errors, deb extra/symlink/control/byte drift, Pre-Depends, and cross-role version conflicts. These fixtures do not claim that any package was acquired or that the pinned image contains the closure. The candidate remains pending a future independently reviewed host capture of the exact image, apt sources/Release bytes, rosdep bytes, all versioned deb bytes, dpkg status, and license/copyright artifacts.