crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 33156f795eed31049d1401c838b27af6a41e983e
parent b190f280d16de7de0a0e1cd3b2a1b0cbcd516b1f
Author: Michael Camilleri <[email protected]>
Date:   Thu,  2 Jul 2026 19:37:21 +0900

Reject over-long grid silhouette payloads

This commit makes GridSilhouette.decode require the decoded payload byte
count to match the exact number of stored grid bits. The previous
decoder truncated to the expected bit count, so a share-link silhouette
with trailing base64url bytes still decoded to the same grid preview.

Now non-canonical silhouette segments fail to decode instead of being
treated as equivalent to the canonical encoder output.

Co-Authored-By: Codex GPT 5.5 <[email protected]>

Diffstat:
MCrossmate/Services/GridSilhouette.swift | 1+
MTests/Unit/GridSilhouetteTests.swift | 11+++++++++++
2 files changed, 12 insertions(+), 0 deletions(-)

diff --git a/Crossmate/Services/GridSilhouette.swift b/Crossmate/Services/GridSilhouette.swift @@ -104,6 +104,7 @@ enum GridSilhouette { let symmetric = tag == "s" || tag == "S" let storedCount = symmetric ? (n + 1) / 2 : n guard let bytes = Self.base64URLDecode(String(payload)) else { return nil } + guard bytes.count == (storedCount + 7) / 8 else { return nil } let bits = Self.unpackBits(bytes, count: storedCount) guard bits.count == storedCount else { return nil } diff --git a/Tests/Unit/GridSilhouetteTests.swift b/Tests/Unit/GridSilhouetteTests.swift @@ -122,4 +122,15 @@ struct GridSilhouetteTests { #expect(GridSilhouette.decode("S2") == nil) // rectangular tag, missing height #expect(GridSilhouette.decode("S21AA") == nil) // height 1 is below minSide } + + @Test("rejects over-long payloads") + func rejectsOverlongPayloads() throws { + let square = try #require(GridSilhouette.encode(side: 3, blocks: [Bool](repeating: false, count: 9))) + #expect(GridSilhouette.decode(square + "AA") == nil) + + var rectangleBlocks = [Bool](repeating: false, count: 6) + rectangleBlocks[0] = true + let rectangle = try #require(GridSilhouette.encode(width: 2, height: 3, blocks: rectangleBlocks)) + #expect(GridSilhouette.decode(rectangle + "AA") == nil) + } }