Skip to content

Verify: decode quoted-printable bodies even when their encoding header is hidden - #29

Merged
Divide-By-0 merged 1 commit into
mainfrom
stack/11-qp-detect-display
Oct 4, 2026
Merged

Divide-By-0 merged 1 commit into
mainfrom
stack/11-qp-detect-display

Conversation

@Divide-By-0

@Divide-By-0 Divide-By-0 commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Human intent

A published proof showed text like customers' samples=E2=80=94whether iden= followed by a line break. The ask: fix how that displays, and fix any proof bug causing it if there is one.

Cause: display only, the proof is fine

  • The email body is quoted-printable (=E2=80=94 is an em dash, and a trailing = is a soft line break).
  • The proof correctly commits the raw signed bytes.
  • The verify page and CLI decoded QP for display only when a revealed Content-Transfer-Encoding: quoted-printable header was visible.
  • This proof hid the whole body except a few paragraphs, which also hid that MIME header, so the raw encoding was shown.

Fix

  • quotedPrintableEvidence(maskedBody) in the site and the CLI returns "header" | "content" | null. It now also recognises QP from the revealed text itself:
    • it needs soft line breaks that don't follow = (base64 padding == at a line end isn't QP), and/or uppercase =XX escapes;
    • the decoded text between hidden bytes must be valid UTF-8. It is validated after decoding because QP can split one character across a soft break (=E2=80= + =94);
    • it never fires when a revealed Content-Transfer-Encoding header names another encoding.
  • Verify page: the caption says when decoding was detected from the text, and there's a Show raw / Show decoded toggle.
  • CLI verify: labels the decoded body; --json adds maskedBodyRaw and bodyDecodedFromQp.
  • Proving is unchanged: nothing about what a proof commits changes.

Evidence

  • This proof (5db31511-…), with the site page in headless Chromium:
    • production shows "samples=E2=80=94whether iden=↵tifiable" with 6 =XX escapes and 18 soft breaks left;
    • this branch shows "samples—whether identifiable, de-identified or aggregated." with 0 escapes and 0 soft breaks, the new caption, and the toggle.
  • Tests: two added in tests/qp.test.ts:
    • hidden header → detected and decoded, including a character split across a soft break (on main this body isn't decoded);
    • no false positives on base64 padding, plain = text, a lone =-ended line, non-UTF-8 escapes, or a declared base64 part.
  • Full suite 46/46 on Node 20; vite build OK. tsc shows the same 5 errors as main, in files this PR doesn't touch.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Email proof verification now recognizes quoted-printable encoding from message content when the encoding header is hidden, and distinguishes it from other encodings.
    • When a message body is decoded for display, you can switch between the decoded text and the original raw text. Verification also explains whether encoding was identified from the visible header or message content.

…ader is hidden

Proofs that hide the body and reveal a few sentences also hide the MIME part's
Content-Transfer-Encoding header, so the verify page and CLI showed raw QP
("samples=E2=80=94whether iden=" + soft line breaks). Detect QP from the revealed text
too (soft breaks not after '=', =XX escapes that decode to valid UTF-8 between hidden
bytes; never when another encoding is declared) and add a 'Show raw' toggle. Display only:
the proof still covers the encoded bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7990a392-f348-434f-95d9-d06ae11bbe17
📥 Commits

Reviewing files that changed from the base of the PR and between 7313499 and f87a925.

📒 Files selected for processing (5)
  • skills/redacted-email-proof/scripts/redacted.mjs
  • src/lib.ts
  • src/pages/VerifyPage.tsx
  • src/utils/qp.ts
  • tests/qp.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The site and CLI now detect quoted-printable content using body evidence when the transfer-encoding header is absent. They retain raw body text, report the evidence source, and let the verification page switch between raw and decoded text.

Changes

Quoted-Printable Detection and Display

Layer / File(s) Summary
Quoted-printable evidence classification
src/utils/qp.ts, skills/redacted-email-proof/scripts/redacted.mjs, tests/qp.test.ts
Both implementations classify evidence from a quoted-printable header or qualifying body content. Content detection checks soft breaks, escapes, and UTF-8 validity between hidden bytes. Tests cover qualifying and rejected inputs.
Proof data and CLI output
src/lib.ts, skills/redacted-email-proof/scripts/redacted.mjs
Proof extraction retains the raw body and evidence source, and uses evidence to select decoded display text. CLI verification output includes raw body data and labels quoted-printable decoding.
Verification page display
src/pages/VerifyPage.tsx
The page stores the evidence and raw body. When quoted-printable decoding occurred, a toggle switches between raw and decoded text, with explanatory text that distinguishes header-based from content-based detection.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant VerifyPage
  participant extractMaskedDataFromProof
  participant quotedPrintableEvidence
  VerifyPage->>extractMaskedDataFromProof: Load proof data
  extractMaskedDataFromProof->>quotedPrintableEvidence: Classify masked body
  quotedPrintableEvidence-->>extractMaskedDataFromProof: Return evidence
  extractMaskedDataFromProof-->>VerifyPage: Return raw and display body data
  VerifyPage->>VerifyPage: Toggle raw or decoded text
Loading

Merge Risk: ⚪ Minimal · up to f87a9

The change decodes quoted-printable bodies when the encoding header is hidden, and adds a raw/decoded toggle. I found no concrete merge-blocking risk.

Security Architecture Review

Security architecture risk: 🔵 Low · up to f87a9

The change operates on already-public, masked proof content. Raw text remains available, decoding is disclosed, and proof verification and redaction remain separate from presentation. Residual risk concerns ambiguous encoding detection rather than new access to private data or increased authority.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The examined exposure is interpretation of individual proof bodies in the browser and CLI. Content inference can change displayed text, but the changed code does not add a private-email source, privileged operation, or cross-service write.

Trust Boundaries and Controls

  • observed — Both browser display modes use MaskedText, which renders ordinary content as React text spans and null-byte runs as redaction blocks. The existing masked decoder preserves unknown escape content as a null marker rather than reconstructing hidden bytes.
  • observed — A body containing both quoted-printable and another transfer-encoding declaration still selects QP decoding. The base browser classifier and CLI declaration check already accepted any matching QP declaration, so this is a pre-existing interpretation limitation, not an introduced verification or masking bypass.

Resilience and Maintainability Implications

  • observed — CLI verification retains separate proof-validity and key-binding results and failure exit paths. The new raw-output and decoding fields do not replace these checks; undecodable invalid proofs retain the existing fallback output behavior.

Hardening Proposals

  • proposed — Describe content-based detection as a display inference without asserting that a MIME header was necessarily hidden. Preserve raw access and add shared ambiguity cases for literal escape-looking text and conflicting declarations to constrain future presentation-control drift.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: detecting and decoding quoted-printable bodies when the encoding header is hidden.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Divide-By-0
Divide-By-0 merged commit ed245ba into main Oct 4, 2026
4 checks passed
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