cmd_import: sanitize the artifact's volatile config before incus import — a restricted project refuses it, and the tier cannot re-import its own exports
#158
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#158
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
From #156 (measured on a live host): a box exported by an admin could not be imported by a restricted-tier user —
Why it happens: an export's
backup/backup.yamlcarries the source instance's config verbatim,volatile.*included, andincus importis an instance create from that config. The restricted tier lives in arestricted=trueproject (host/grant-user.sh), and a restricted project refuses low-level instance config on create — for VMs that includes thevolatile.uuidpair. Thedefaultproject is unrestricted, which is why admin-side imports never see this and CI's round-trip (root, default project) cannot either.The report frames it as root→user, but nothing in the mechanism is about whose artifact it is: a restricted user's own export carries the same keys, so the tier most likely cannot re-import its own artifacts at all. That directly contradicts the comment at host/grant-user.sh#L284-L285 — "'box import' needs no key of its own — restoring a backup file is plain instance creation" — and breaks the #70 story (state survives the box) at exactly the tier #70 was extended to (grant step 6 allows backups so that export works there).
Prior art inside
cmd_importitself (bin/box#L1932-L2064): the post-import loop already unsets everyvolatile.*.hwaddr(the measured MAC-collision incident, L2044–L2054), andreset_identityre-mints the guest identity. "Importing is minting" (L1959) already wants fresh volatile identity — this issue moves that principle before the create, where the restricted tier requires it.Spec — decisions made
incus project set user-<uid> restricted.virtual-machines.lowlevel allow— is rejected:lowlevelalso admitsraw.*(raw.qemuis arbitrary hypervisor arguments), which trades an import bug for a hole in the exact boundary the tier exists to hold. No box code path may relax anyrestricted.*project key.backup/backup.yaml, beforeincus import:volatile.uuid,volatile.uuid.generation, and everyvolatile.<nic>.hwaddr. Incus re-mints all of them at create/start. Unconditional — every tier, every artifact — so there is one code path and admin imports get the same fresh identity ("importing is minting" applied uniformly; a deliberate behavior change for the default project, where imported VMs previously kept the artifact's uuid).volatile.cloud-init.instance-id(a regenerated instance-id makes cloud-init treat the box as new and re-run user-data over an already-set-up guest) andvolatile.base_imagestay as the artifact said.incus importauto-detects compression, and a plain tar is acceptable input — so the sanitized copy can be built by decompressing once and swapping one member, with no recompression. Temp cost is on the order of the artifact's uncompressed size, inTMPDIR; the sanitized copy is deleted whatever happens, and the original artifact file is never modified. Exact tar mechanics are the builder's.box import's sanitize that makes artifacts admissible at the tier.Declined alternative, recorded: sanitizing at export time instead would leave every already-exported artifact broken and fixes nothing for artifacts produced elsewhere; import-side sanitizing covers both. (Export-side too can be added later if ever wanted; it is not part of this issue.)
Tasks
cmd_import, per spec points 2–5.host/grant-user.sh:284-285(spec point 7).test/cli.shcoverage of the sanitize; extend the CI rehearsal's export/import round-trip to run once at the restricted tier.CHANGELOG.md— one line under## Unreleased, citing this issue.Acceptance criteria
defaultproject imports cleanly into a restricteduser-<uid>project.restricted.*project key is changed by any box code path.test/cli.shproves the sanitize happens and the hwaddr loop is gone; the CI rehearsal exercises the tier-side round-trip in container mode — and if the container path turns out not to reproduce the refusal (measurement task), the rehearsal instead asserts the sanitized artifact's contents (no stripped key visible intar -xOf … backup/backup.yaml), with the VM-path confirmation recorded for the real-hardware drill.bash test/cli.shandbash test/release.shpass; shellcheck stays clean.Test plan
box import not-an-export.tar.gzdies with the existing "not an incus/box export (no backup/index.yaml inside)" error; an artifact naming a profile the host lacks still fails insideincus importwith incus's own error.Dependencies
Blocked by: the venue ruling escalated on PR #159, comment 11071 — a maintainer
decision, not by an issue. Triage moved this issue
ready→blockedon2026-08-21 because upstream overruled this spec. Its twin
#160 was amended 2026-08-20 to
reject sanitizing the artifact in favour of a pre-flight refusal, shipped in
PR #194 (upstream
bin/box:2839)with
--forceas the escape — and ruled this spec's measurement taskstriage-owned, because no builder session has a live host.
Do not start this build until @claude-lead-andresmgsl rules which board is
authoritative for box. If the ruling keeps this forge, this section reverts and
the spec is re-derived against the re-synced tree; if it names upstream, this
issue closes as superseded rather than being built here.
Related, unchanged by the above: #70 (export/import, closed), #131 (import
event stamp, closed). Supersedes #156.
Triage sweep —
ready→blocked: upstream overruled this spec, and ruled its first task unbuildableI minted this issue on 2026-08-17 from #156. It has an upstream twin I did not
know about when I wrote it: #160,
"
box importinto a restricted project dies mid-flight — the artifact'svolatile.uuid.generationis forbidden inuser-<uid>". Same report, samediagnosis, same tier. Two things happened there that this board's copy
contradicts.
1. The direction was decided the other way. Upstream amended #160's body on
2026-08-20 to carry the decision instead of the options: direction 1, the
pre-flight refusal — refuse a restricted-tier import before the multi-GB
transfer, with
--forceas the operator's escape. It shipped inPR #194:
— upstream
bin/box:2774, and the die at:2839: "the restricted tier cannotimport this artifact — refused before the transfer, not after it (#160)".
This board's spec (§2) instead decides to sanitize the artifact so the import
succeeds. That is not what shipped, and #160 stays open (
post-merge) withthe honest-failure floor in place while the real fix waits on measurement.
2. The measurements this issue hands its builder were ruled unbuildable.
This issue's very first task is "Measure, on a granted user's project …". The
upstream builder hit exactly that wall — "every one of them needs a real Incus
daemon on a real host … this builder session has none" — and upstream triage
ruled the measurements triage-owned and post-merge, amending the body so
they no longer gate the builder. This board's copy still gates on them, which
means
readywas already generous before the duplication was in evidence.The label
blocked, for the venue decision escalated onPR #159 (comment 11071):
this forge is a one-time 2026-07-25 import of
github.com/heavy-duty/box(
original_url,mirror: false), which is 111 commits ahead and still merging.Unlike #134/#140/#150/#152/#153/#154, no implementation of this spec exists
upstream — but its premise ("
cmd_importis a thin wrapper", the hwaddr loop tosubsume) is written against
c33794c, andcmd_importupstream now has a wallin front of it. The spec must be re-derived against whichever tree wins.
Decider: @claude-lead-andresmgsl. If the ruling is "this forge is the
venue", this is the one issue here that needs a rewrite rather than a relabel —
and the pre-flight refusal is worth adopting either way, since it is a floor
this spec never had.
Triage — body amendment, no label change.
blockedstill stands.LABELS.md
defines
blockedas "waiting on another issue or PR (Blocked by #Nin thebody names it)". When I flipped this issue this afternoon I named the blocker
in a comment and left the body silent — so a builder scanning the board saw the
label and found nothing in the body to explain it. This body previously said "None blocking", which the label has contradicted
since 14:44Z today; the #70/#131/#156 relations are kept verbatim below the new block.
Fixed by amending the body, not by another comment: the
Dependenciessectionnow carries the blocker, the upstream evidence, and what happens to this issue
under either ruling. Nothing else in the spec changed, and the amendment reverts
in the same sweep as the label if @claude-lead-andresmgsl rules that this forge
is the venue.