blueprint/core.md states the rule twice, in the two places it matters:
An unwalkable remainder is truncated, never refused: the carried set is attacker-influenced, so failing the cut would let a committed write-grantee block the rotation that revokes them.
A re-seal handed an over-length link drops it rather than failing, so the produce side can never emit a body its own decoder refuses.
The engine implements neither for the envelope's carried unknown fields. crates/engine/src/net/author.rs copies carried_unknown and carried_epoch_tag_unknown verbatim into the re-authored envelope, and the MAX_RESOLVED_RECORD_BYTES check in encode runs after that copy. The two fields come straight off a resolved record — Drain::load_child_node, load_child_folder and publish_node in crates/engine/src/sync/drain.rs all thread them through — with no truncation anywhere in the crate.
A resolved record may itself be up to MAX_RESOLVED_RECORD_BYTES (read_block's own ceiling). So a co-member holding the scope write seed for a shared folder, or a compromised sibling device of the same account, can publish a near-ceiling record at a node's name and make every subsequent re-author of that node exceed the ceiling. That is the attacker-influenced produce-side refusal the blueprint forbids: the owner's publishes at that node stop, and the only exit is the attacker shrinking their own record.
PR #1320 (#1308) bounds the damage — the halt is charged and the op dead-letters with its version preserved rather than spinning forever — but it does not remove the trigger, and says so.
Two adjacent gaps found alongside it
Ordinary folder growth reaches the same ceiling with no attacker involved. There is no folder child-count limit and no name-length limit in crates/core or crates/engine. A folder record inlines a ChildRef per child, so somewhere around 15-20k children in one folder the folder's own record crosses 2 MiB and every write into it dead-letters. #1301 bounds the write body's total encoded size; this is the read-body/folder side of the same question, and it wants a member-facing answer ("split this folder"), not only a bound.
Command::Create accepts an arbitrary-length name. crates/engine/tests/write_plane.rs now relies on that to build the over-ceiling case, which is a fair test lever but a poor command contract: a single name can be made larger than any record that could carry it.
Acceptance criteria
Found by the /security-review gate on PR #1320.
Part of #655
blueprint/core.mdstates the rule twice, in the two places it matters:The engine implements neither for the envelope's carried unknown fields.
crates/engine/src/net/author.rscopiescarried_unknownandcarried_epoch_tag_unknownverbatim into the re-authored envelope, and theMAX_RESOLVED_RECORD_BYTEScheck inencoderuns after that copy. The two fields come straight off a resolved record —Drain::load_child_node,load_child_folderandpublish_nodeincrates/engine/src/sync/drain.rsall thread them through — with no truncation anywhere in the crate.A resolved record may itself be up to
MAX_RESOLVED_RECORD_BYTES(read_block's own ceiling). So a co-member holding the scope write seed for a shared folder, or a compromised sibling device of the same account, can publish a near-ceiling record at a node's name and make every subsequent re-author of that node exceed the ceiling. That is the attacker-influenced produce-side refusal the blueprint forbids: the owner's publishes at that node stop, and the only exit is the attacker shrinking their own record.PR #1320 (#1308) bounds the damage — the halt is charged and the op dead-letters with its version preserved rather than spinning forever — but it does not remove the trigger, and says so.
Two adjacent gaps found alongside it
Ordinary folder growth reaches the same ceiling with no attacker involved. There is no folder child-count limit and no name-length limit in
crates/coreorcrates/engine. A folder record inlines aChildRefper child, so somewhere around 15-20k children in one folder the folder's own record crosses 2 MiB and every write into it dead-letters. #1301 bounds the write body's total encoded size; this is the read-body/folder side of the same question, and it wants a member-facing answer ("split this folder"), not only a bound.Command::Createaccepts an arbitrary-length name.crates/engine/tests/write_plane.rsnow relies on that to build the over-ceiling case, which is a fair test lever but a poor command contract: a single name can be made larger than any record that could carry it.Acceptance criteria
blueprint/core.md.DeadLetterReasonfor a size refusal. Today it is indistinguishable from a transport outage — both surface asAttemptsExhausted— so a member whose folder crossed the ceiling is told the same thing as one whose network was down, and neither learns the remedy.Found by the
/security-reviewgate on PR #1320.Part of #655