Skip to content

UID2-7505: Detect unencrypted JSON request bodies in v2 envelope parsing#2660

Open
swibi-ttd wants to merge 13 commits into
mainfrom
swi-UID2-7505-detect-unencrypted-json
Open

UID2-7505: Detect unencrypted JSON request bodies in v2 envelope parsing#2660
swibi-ttd wants to merge 13 commits into
mainfrom
swi-UID2-7505-detect-unencrypted-json

Conversation

@swibi-ttd

@swibi-ttd swibi-ttd commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Improves the v2 envelope parse error messages. A common integration mistake — sending base64-encoded plain JSON (e.g. via curl) instead of an encrypted request envelope — currently returns the opaque Invalid body: Version mismatch., which gives no hint that the payload must be encrypted.

Changes

  1. Detect unencrypted JSON bodies. When envelope parsing fails and the body parses as JSON (an encrypted envelope never does), return a specific error: "Invalid body: The request body is unencrypted JSON. It must be an encrypted request envelope. See <docs> for encryption and decryption code examples." Runs only on the error path.
  2. Rename the Version mismatch error, which reads as an operator/API version problem: "Invalid body: Invalid request envelope format version: received X, must be 1." (VERSION constant → ENVELOPE_FORMAT_VERSION.)
  3. Append a link-only docs pointer to the other envelope parse errors (not valid base64 / too short / wrong key): "See <docs> for details on the request envelope format." Unlike a cause-asserting hint, this cannot mislead the wrong-secret or proxy-corruption cases.
  4. Fix error precedence in the octet-stream → base64 fallback. When both interpretations of an octet-stream body fail, the binary error was always returned — hiding the base64 path's more accurate diagnosis whenever the body was actually base64 text. A binary envelope can never be valid base64 (its first byte is the 0x01 envelope version byte, not a base64 character), so if the fallback got past base64 decoding, the body was base64 text and the fallback's error is returned instead.

Docs links are scope-aware (unifiedid.com / euid.eu); IdentityScope is plumbed from V2PayloadHandler. All existing leading phrases are preserved, and nothing in uid2-operator, uid2-e2e, uid2-validator, or the client SDKs matches the old Version mismatch string.

Testing

New tests for base64-encoded, raw, and short unencrypted JSON bodies, genuine version mismatch, the EUID docs link, and both directions of the octet-stream fallback precedence. mvn clean test: 757 tests, 0 failures.

🤖 Generated with Claude Code

@swibi-ttd
swibi-ttd force-pushed the swi-UID2-7505-detect-unencrypted-json branch from 0e75e91 to 94170b3 Compare July 23, 2026 03:07
swibi-ttd and others added 13 commits July 24, 2026 10:19
When a v2 request body fails envelope parsing and the (decoded) bytes parse
as JSON, return a specific error explaining the body must be an encrypted
request envelope, with a link to the encryption/decryption docs (unifiedid.com
or euid.eu depending on identity scope). An encrypted envelope never parses
as JSON, so the check only fires for genuinely unencrypted payloads, and the
existing error messages remain byte-identical for all other causes
(corrupted envelope, proxy-transformed body, wrong client secret).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…stant

'Version mismatch' reads as an operator/API version problem. Rename the
constant to ENVELOPE_FORMAT_VERSION and report the error using the docs'
own term ('version of the envelope format'), including the received byte:
'Invalid body: Invalid request envelope format version: received X, must be 1.'

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A neutral pointer to the envelope-format docs, appended after each error's
cause statement; the leading phrase of every message is preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When both the binary and base64 interpretations of an octet-stream body fail,
the binary error was always returned - hiding the base64 path's more accurate
diagnosis (e.g. unencrypted JSON) whenever the body was actually base64 text.
A binary envelope can never be valid base64 (its first byte is the 0x01
envelope version byte, not a base64 character), so if the fallback got past
base64 decoding, the body was base64 text and the fallback's error applies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the general half of the precedence rule: an octet-stream body that is
valid base64 of a corrupt (non-JSON) envelope gets the fallback's error about
the decoded bytes, not the binary parse error about the base64 text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the failedBase64Decoding flag: instead of parsing the octet-stream
body both ways and arbitrating between the two errors afterwards, the fallback
first checks whether the body base64-decodes. If not, the body was truly
binary and the binary parse error stands; if it does, the decoded bytes are
parsed once and that result is authoritative, valid or not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two 'Fallback failed' lines were indistinguishable in the logs. Each of
the three fallback outcomes (not base64, parsed as base64, base64 but invalid
envelope) now logs a distinct message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ore rationale

- Add a test for the base64-over-octet-stream success path (rewritten in the
  Option B restructure): a valid envelope decodes, parses, and the content type
  is rewritten to text/plain. Extracts the valid-envelope fixture and ClientKey
  to shared members.
- Handle a null body before the base64 probe so a missing body no longer logs
  the misleading 'body is not base64' fallback line.
- Restore the rationale for why base64-decodability is the discriminator, fix a
  typo, and reword the stale success-branch comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add a test for the wrong-client-secret decrypt-failure branch.
- Replace the tautological assertEquals(unencryptedJsonErrorMessage(...), ...)
  assertions: one canonical test asserts the full literal message; the rest
  assert the stable leading phrase plus the docs link.
- Add an EUID unencrypted-JSON test through the HTTP entry point (parseRequest).
- Extract the shared docs path to a DOCS_PATH constant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@swibi-ttd
swibi-ttd force-pushed the swi-UID2-7505-detect-unencrypted-json branch from 049c946 to 11cc5bd Compare July 24, 2026 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant