Close the RSBK name-collision class: ASCII case folding, UTF-8 well-formedness, nested-path traversal

All three are format-locked and validated on encode and decode. Repeated known
keys now reject at the root and inside an entry rather than last-wins.
This commit is contained in:
2026-08-02 08:44:24 -04:00
parent 1aebf51938
commit 3909b1072c
8 changed files with 442 additions and 74 deletions
+37 -3
View File
@@ -142,9 +142,7 @@ static void testEncodeRefusesWhatManifestRefuses() {
m.entries[0].fileName = "../evil.wav";
CHECK(!encodePackage(m).has_value());
// A zero-length entry cannot round-trip through the shell's filesystem
// seam (src/shell/package's appendPayload refuses an empty payload) — the
// format layer must never produce one.
// See src/core/package/CLAUDE.md for the shell seam that forces this.
PackageManifest zeroLen = fixture(1, 1);
zeroLen.entries[1].byteLength = 0;
CHECK(!encodePackage(zeroLen).has_value());
@@ -185,6 +183,41 @@ static void testTruncationAtEveryByteOffsetIsMalformed() {
CHECK(decodePackage(file, file.size() - 1).status == PackageReadability::Malformed);
}
// --- header defaults ---------------------------------------------------------
// The 0/0 defaults are not the current ladder pair, so a header that never
// parsed cannot be mistaken for a plausible 1/1. They mean "unset", NOT "the
// decode failed": a decode that got past the header reports the real pair
// alongside its Malformed verdict.
static void testHeaderDefaultsMeanUnparsed() {
CHECK(PackageHeader{}.formatVersion == 0);
CHECK(PackageHeader{}.minReaderVersion == 0);
CHECK(PackageHeader{}.writerVersion.empty());
// Failed before the header: bad magic, and a semver truncated mid-string.
std::vector<std::uint8_t> badMagic = rawHeader(1, 1, "1.0.0");
appendManifest(badMagic, "{}");
badMagic[0] = 'Z';
const DecodedPackage magic = decodePackage(badMagic, badMagic.size());
CHECK(magic.status == PackageReadability::Malformed);
CHECK(magic.header == PackageHeader{});
std::vector<std::uint8_t> cutSemver = rawHeader(1, 1, "1.0.0");
cutSemver.resize(18);
CHECK(decodePackage(cutSemver, cutSemver.size()).header == PackageHeader{});
// Failed after it: a same-version package with a corrupt manifest is
// Malformed, and its header is fully populated.
std::vector<std::uint8_t> corrupt =
rawHeader(kPackageFormatVersion, kPackageMinReaderVersion, "1.0.0");
appendManifest(corrupt, "not json");
const DecodedPackage late = decodePackage(corrupt, corrupt.size());
CHECK(late.status == PackageReadability::Malformed);
CHECK(late.header.formatVersion == kPackageFormatVersion);
CHECK(late.header.minReaderVersion == kPackageMinReaderVersion);
CHECK(late.header.writerVersion == "1.0.0");
}
// --- version ladder: TooNew refuses whole ------------------------------------
static void testTooNewProducesNoManifest() {
@@ -365,6 +398,7 @@ int main() {
testEncodeDecodeRoundTrip();
testEncodeRefusesWhatManifestRefuses();
testTruncationAtEveryByteOffsetIsMalformed();
testHeaderDefaultsMeanUnparsed();
testTooNewProducesNoManifest();
testAdditiveUnparseableManifestIsTooNew();
testNewerAdditiveFormatReads();