Skip to content

BackupProvenance: Kotlin emits invalid/non-byte-parity JSON for control characters #86

Description

@bhelm

Finding

Bounded P2 parity-contract defect with low normal-production reachability.

Kotlin BackupProvenance.sortedJsonObject escapes only backslash and quote. String values containing any JSON control character U+0000 through U+001F are emitted with the raw control byte, producing invalid JSON and diverging from Swift JSONSerialization(.sortedKeys).

Normal shipped inputs are currently build-controlled (bundle/BuildConfig version, numeric build, fixed platform), so no ordinary production trigger, data loss, or P0/P1 impact has been established. Reachability is limited to malformed/custom build metadata, tampered/restored version preferences, or direct API callers. The public helpers nevertheless accept arbitrary strings and explicitly promise byte parity.

Exact RED

Add the same paired test on Swift and Kotlin:

  • AppVersionEvent.payloadJson(from: "10.1\n0", to: "10.1\t1\u0001", schemaVersion: 30)
  • Require valid JSON, exact decoded-string roundtrip, sorted keys, and the canonical escaped bytes:
    {"from":"10.1\\n0","schemaVersion":30,"to":"10.1\\t1\\u0001"}

Swift passes today. Kotlin currently emits literal LF, TAB, and U+0001 and a standards-compliant JSON parser rejects it.

Acceptance

  • Kotlin produces valid JSON for every accepted string containing any character in U+0000...U+001F.
  • Decoding round-trips the original string values exactly.
  • Swift/Kotlin outputs remain byte-identical for equal inputs, with sorted keys and unquoted numeric values.
  • Coverage spans the complete U+0000...U+001F range, not only newline and tab.
  • Keep the change scoped to the pure provenance JSON helper; no backup-container or restore behavior change is required.

Upstream routing / provenance

This originated in upstream ryanbr/noop PR ryanbr#1422 (commit d9aae8cb), whose paired byte-parity tests cover ordinary version values but not control characters. Route the eventual fix upstream after the bounded regression is green.

A final duplicate search in this fork for BackupProvenance/control-character, sortedJsonObject/invalid-JSON, and manifest/byte-parity escaping found no existing issue or PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions