fix(gmail): detect HTML fragments in LooksLikeHTML to prevent signature escaping#879
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 11:20 PM ET / 03:20 UTC. Summary Reproducibility: yes. Source inspection shows current main accepts plain text or HTML signature files, but <style>/ fragments fall through the document-only detector and get escaped; the PR body also includes live raw RFC822 before/after proof.Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the current scoped helper if maintainers accept heuristic fragment detection for explicit signature files and backup cleanup; otherwise narrow the behavior to the signature-file path or require a more explicit HTML signal. Do we have a high-confidence way to reproduce the issue? Yes. Source inspection shows current main accepts plain text or HTML signature files, but <style>/ Is this the best way to solve the issue? Yes, with maintainer acceptance of the heuristic edge cases. The current scoped helper is narrower than the earlier shared-classifier approach and covers the reported bug plus prior false-positive cases. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 57e3283a520a. Label changesLabel justifications:
Evidence reviewedWhat I checked:
internal/gmailcontent/content.go:127, 57e3283a520a)internal/gmailcontent/content.go:139, 185cb7755da4)internal/gmailcontent/lookslikehtml_test.go:26, 185cb7755da4)Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 897723f8dc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| for _, marker := range htmlFragmentMarkers { | ||
| if strings.Contains(trimmed, marker) { |
There was a problem hiding this comment.
Constrain fragment markers to real HTML tags
Because this now treats any occurrence of marker prefixes as HTML, plain text containing angle-bracket placeholders can be misclassified. For example, a --signature-file with Best,\n<PRIVATE_PERSON> matches the "<p" marker here, so readComposeSignatureFile skips escapeTextToHTML and stores the raw value as HTML; mail clients parse <PRIVATE_PERSON> as an unknown tag, so the placeholder disappears instead of being shown literally. The same false positive can affect text/plain message display paths, so the fragment check should require a real tag boundary such as whitespace, >, or / after the tag name.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| for _, marker := range htmlFragmentMarkers { | ||
| if strings.Contains(trimmed, marker) { |
There was a problem hiding this comment.
Preserve text/plain HTML fragments when quoting
When a message has only a text/plain part that contains an HTML fragment such as <div>Hello</div> and no separate text/html part, this new marker match makes LooksLikeHTML return true. In replyInfoFromMessage (internal/cmd/gmail_reply.go:147-150) that suppresses info.Body, while BodyHTML remains empty, so applyQuoteToBodies treats the original body as absent and gog gmail reply --quote drops the quote entirely; before this change, fragments were still included in the quote after escaping. Consider carrying the fragment into BodyHTML or converting it to text instead of using this broader predicate to discard it.
Useful? React with 👍 / 👎.
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
Co-authored-by: Martin Kessler <[email protected]>
4fbfb44 to
185cb77
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 185cb7755d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // is required: > (close tag), / (self-closing), or whitespace (start of attributes). | ||
| // Punctuation like @, ., : does not count as a boundary. | ||
| var htmlFragmentPattern = regexp.MustCompile( | ||
| `<!--|</?(?:p|br|div|span|table|tr|td|section|blockquote|a|img|font|style)[\s/>]`, |
There was a problem hiding this comment.
Detect inline-only HTML signatures
When a local --signature-file is valid HTML made only of inline tags such as <b>Peter</b> or <strong>Peter</strong>, this whitelist does not match, so readComposeSignatureFile falls through to the plaintext branch and sends escaped markup (<b>...) instead of an HTML signature. The option is documented as accepting plain text or HTML, so valid inline-only fragments need to be detected here or handled by a signature-specific parser/path.
Useful? React with 👍 / 👎.
|
Landed as 947b7cfd. Validation:
Maintainer adjustments: restricted fragment detection to signature-file and backup-export inputs, preserved the existing document-only classifier for message display/reply quoting, added direct signature-file coverage, and added backup regression coverage for literal Caveat: I did not repeat the live Gmail send; the contributor's raw-message proof remains the live delivery evidence. |
What
LooksLikeHTMLonly detected document-level HTML (<!doctype>,<html>,<head>,<body>,<meta>). When--signature-filepointed at a file starting with<style>or<table>— common in HTML email signatures — it failed detection and fell through toescapeTextToHTML, which HTML-escaped the entire signature. Recipients saw<style>and<table>as visible text instead of rendered HTML.Why
readComposeSignatureFileininternal/cmd/gmail_send_signature.go:86callsLooksLikeHTMLto decide whether to preserve raw HTML or escape it. A<style>-prefixed signature file is valid HTML but not a full document, so it was misclassified as plain text and escaped.Changes
internal/gmailcontent/content.go: ReplaceLooksLikeHTMLwith a compiled regex (htmlFragmentPattern) that matches real HTML tags and comments using\bword boundary after tag names. This prevents false positives on angle-bracketed plain text like<path>,<PRIVATE_PERSON>, or<[email protected]>.internal/cmd/backup_export_gmail.go: Delete deadlooksLikeHTMLFragmentduplicate —LooksLikeHTMLis now a superset. SimplifybackupEmailMarkdownTextto callLooksLikeHTMLalone.internal/cmd/gmail_reply.go: When atext/plainpart contains HTML fragments and there is no separatetext/htmlpart, carry the fragment intoBodyHTMLso--quotereplies don't lose the original message body.internal/gmailcontent/lookslikehtml_test.go: Add negative tests for angle-bracketed plain text (<path>,<project>,<[email protected]>,<PRIVATE_PERSON>,<quick brown>).Tests
TestLooksLikeHTML_DocumentPrefixes— existing document-level detection unchangedTestLooksLikeHTML_FragmentMarkers—<style>,<table>,<div>,<p>,<br>, etc. detected as HTMLTestLooksLikeHTML_PlainText— plain text, bare URLs,<path>,<PRIVATE_PERSON>,<[email protected]>,a < bnot misdetectedReal Behavior Proof
Before fix (broken email, Gmail message ID
19ef630fa26b22a2):Raw RFC822 text/html section shows escaped signature:
After fix (verification email, Gmail message ID
19ef6defcf54663e):Sent
gog gmail send --to [email protected] --body-html "$BODY" --signature-file signature-martin-thread-short-no-ps.html. Raw RFC822 text/html section:Signature HTML is raw (
<style type="text/css">), not escaped (<style>). Fix confirmed.AI Assistance
This PR was authored with AI assistance (Claude Code / OpenCode + GLM 5.2). The bug was discovered when an OpenClaw agent sent a BIPA legal acknowledgement email to counsel at GRSM and the HTML signature was delivered as escaped visible text. Root cause traced through the raw RFC822 message to
html.EscapeStringoutput matching exactly.