Skip to content

fix: sanitize peer-controlled display text#447

Merged
mubarakcoded merged 5 commits into
masterfrom
pip/whitenoise-mac-387
Jul 13, 2026
Merged

fix: sanitize peer-controlled display text#447
mubarakcoded merged 5 commits into
masterfrom
pip/whitenoise-mac-387

Conversation

@agent-p1p

@agent-p1p agent-p1p commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Fixes #387

Summary

  • Added PeerDisplayText to strip Unicode format controls from peer-controlled display strings before rendering.
  • Applied sanitization to chat-list titles/previews, message sender labels/reply contexts, group-system event names/text, and local notification sender/group names.
  • Wrapped sanitized peer display fragments in first-strong isolate/PDI when interpolating them into system text or preview templates.
  • Added regression coverage for bidi-control stripping in sender names, group rename names, chat-list rows, legacy group-system payloads, and notification output.

Verification

  • git diff --check — passed
  • bash -n scripts/ci/macos-sanity-checks.sh — passed
  • conflict-marker sweep with git grep -n -E '^(<<<<<<<|=======|>>>>>>>)' -- . — passed
  • raw bidi-control sweep over changed source files — passed; tests use escaped literals
  • scripts/ci/macos-sanity-checks.sh — not runnable on this Linux host: xcodebuild: command not found
  • macOS CI commands (xcrun swift-format, xcodebuild test/build/analyze) — not runnable on this Linux host; xcodebuild, xcrun, swift, swift-format, and plutil are missing

Sensitive paths

none


Open in Stage

Summary by CodeRabbit

  • New Features

    • Display names and message previews are now rendered more safely, with improved handling of special Unicode characters.
    • Group chats, notifications, and member details now use cleaner, more consistent names in the UI.
  • Bug Fixes

    • Fixed cases where hidden formatting characters could affect chat titles, replies, rename messages, and notification text.
    • Improved fallback behavior when names are blank or partially unavailable.
  • Tests

    • Added coverage for name sanitization and bidirectional text handling across chat, group, and notification flows.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mubarakcoded, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1a7e36e4-a277-4898-a7f9-fdc3b14c1834

📥 Commits

Reviewing files that changed from the base of the PR and between b61bdeb and fcd38d9.

📒 Files selected for processing (10)
  • whitenoise-mac/Core/MarmotMapping.swift
  • whitenoise-mac/Core/PeerDisplayText.swift
  • whitenoise-mac/Core/WorkspaceState+NewChat.swift
  • whitenoise-mac/Core/WorkspaceState+Notifications.swift
  • whitenoise-mac/Core/WorkspaceState.swift
  • whitenoise-mac/Models/MessengerModels.swift
  • whitenoise-mac/Views/GroupViews.swift
  • whitenoise-macTests/PeerDisplayTextTests.swift
  • whitenoise-macTests/PureValueTests.swift
  • whitenoise-macTests/whitenoise_macTests.swift

Walkthrough

Introduces a PeerDisplayText utility to sanitize peer-controlled strings by stripping Unicode format/bidi-control scalars and wrapping text in isolation markers for safe interpolation. Applies this sanitization across chat/message mapping, notifications, workspace state, and models, replacing prior trimming/firstNonBlank logic, with corresponding test updates.

Changes

Peer-controlled name sanitization

Layer / File(s) Summary
PeerDisplayText sanitizer utility
whitenoise-mac/Core/PeerDisplayText.swift
New enum with sanitize(_:) removing Unicode format scalars and templateFragment(_:) wrapping sanitized text in bidi isolation markers.
Chat/message mapping sanitization
whitenoise-mac/Core/MarmotMapping.swift
Chat titles, preview sender prefixes, group system text, rename text, account names, and reply sender names now use PeerDisplayText.sanitize/templateFragment instead of trimming/firstNonBlank/nonBlank.
Notification content sanitization
whitenoise-mac/Core/WorkspaceState+Notifications.swift
Sender name derived via sanitize with "Someone" fallback; group-invite and new-message title/body logic switched to groupName ?? senderName/sanitized sender template.
Workspace state and model sanitization
whitenoise-mac/Core/WorkspaceState.swift, whitenoise-mac/Core/WorkspaceState+NewChat.swift, whitenoise-mac/Models/MessengerModels.swift, whitenoise-mac/Views/GroupViews.swift
Group member/account names, group snapshot names, new-chat recipient display names, and the remove-member confirmation dialog now sanitize peer-controlled strings before use.
Sanitization test coverage
whitenoise-macTests/PeerDisplayTextTests.swift, whitenoise-macTests/PureValueTests.swift, whitenoise-macTests/whitenoise_macTests.swift
New and updated tests verify sanitize/templateFragment behavior, chat item/timeline sanitization, group snapshot sanitization, and bidi-isolated expectations in notification/message strings.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.11% which is insufficient. The required threshold is 80.00%. 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 is concise and accurately captures the main change: sanitizing peer-controlled display text.
Linked Issues check ✅ Passed The PR applies bidi-control sanitization and template isolation to peer/group names across sender labels, system messages, notifications, and previews as required by #387.
Out of Scope Changes check ✅ Passed The changes stay focused on display-text sanitization, tests, and supporting helpers, with no obvious unrelated feature work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pip/whitenoise-mac-387

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.

@stage-review

stage-review Bot commented Jul 9, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 5 individual chapters for you:

Title
1 Introduce PeerDisplayText for string sanitization
2 Sanitize chat list and timeline mapping
3 Sanitize workspace state and group details
4 Sanitize local notification content
5 Update tests for sanitization and isolation
Open in Stage

Chapters generated by Stage for commit fcd38d9 on Jul 13, 2026 1:11pm UTC.

@agent-p1p

Copy link
Copy Markdown
Contributor Author

Pip adversarial review — changes requested

Blocking findings:

  1. The fix still leaves peer-controlled display/group names unsanitized on the new-chat and group-details identity surfaces.

    The PR sanitizes the timeline/chat-list/notification mapping paths, but the same attacker-controlled Nostr/profile strings still flow raw through the new-chat directory:

    • whitenoise-mac/Core/WorkspaceState+NewChat.swift:61-72 stores profileDisplayName, profileName, and directoryDisplayName directly in NewChatRecipient.displayName.
    • whitenoise-mac/Models/MessengerModels.swift:2092-2098 only whitespace-trims NewChatRecipient.title.
    • whitenoise-mac/Views/ComposerViews.swift:642,649 renders that title in the pending/added recipient cards, and WorkspaceState+NewChat.swift:183-187 can reuse it as the direct/group fallback title.

    The group-management/details surfaces have the same gap:

    • whitenoise-mac/Core/WorkspaceState.swift:1497-1502 stores raw member.displayName / member.account in GroupMemberItem.displayName.
    • whitenoise-mac/Core/WorkspaceState.swift:1521 stores raw details.group.name in GroupDetailsSnapshot.name.
    • whitenoise-mac/Views/GroupViews.swift:46,505,575 renders/interpolates those values.

    A member named \u{202E}Trusted Admin\u{2066} still renders reordered/spoofed in those UI paths. That is in scope for Security/Privacy: peer-controlled display names & group names are rendered with no Unicode bidi-control sanitization (identity spoofing) #387: the issue explicitly calls out propagation beyond the transcript, including the new-chat directory, and the title is about peer-controlled display names/group names generally. Apply the same PeerDisplayText.sanitize boundary there, and use templateFragment when embedding sanitized peer/group names into surrounding prose.

  2. CI is currently red.

    GitHub Mac CI PR Checks failed before tests ran. The failed step is Swift format lint:

    • whitenoise-macTests/PeerDisplayTextTests.swift:81,91,106,126,156 — trailing comma in single-line collection/call literals.
    • whitenoise-macTests/PeerDisplayTextTests.swift:136 — line too long.
    • whitenoise-macTests/whitenoise_macTests.swift:3506 — line too long.

    This needs a formatting-only follow-up commit before the PR can pass the review/merge gate.

Suggestion:

  • Consider pushing the sanitizer closer to the model-boundary helpers for peer-controlled display strings instead of fixing individual render call sites one at a time. The current split already missed two UI surfaces; a smaller number of boundary functions would be harder to bypass.

Checks run/read:

  • gh pr view 447 --json ... at head 2756265ddd8f68f5d1a93144cc272a5fbc52e5e0.
  • gh pr checks 447 and gh run view 29028094213 --log-failed — PR Checks failed, Static Analysis passed.
  • git diff --check origin/master...HEAD — passed locally.
  • bash -n scripts/ci/macos-sanity-checks.sh — passed locally.
  • conflict-marker sweep — none.
  • raw U+202A–U+202E / U+2066–U+2069 sweep over changed production source — none.

No out-of-scope follow-up filed; the unsanitized surfaces above are part of this issue.

@agent-p1p
agent-p1p force-pushed the pip/whitenoise-mac-387 branch from 8d84bc0 to b61bdeb Compare July 9, 2026 15:25
@agent-p1p
agent-p1p marked this pull request as ready for review July 9, 2026 15:32
@agent-p1p

Copy link
Copy Markdown
Contributor Author

Addressed Pip review findings in b61bdeb:

  • sanitized new-chat recipient display names at the resolution/model boundary
  • sanitized group-details member names/account labels and group names; isolated the remove-member confirmation template
  • fixed swift-format failures

Current head checks are green and the PR is marked ready for review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
whitenoise-macTests/PeerDisplayTextTests.swift (1)

21-236: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

New test struct isn't part of the single serialized suite.

This file introduces struct PeerDisplayTextTests without @Suite(.serialized), and it isn't merged into the existing whitenoise_macTests suite. Per the repo's Swift Testing guideline, this can run in parallel with (and independently of) the serialized suite, and the runner's -only-testing: filter won't reliably target its functions either. This split also forces duplicated helpers (isolated, timelineMessage, groupSystemEvent) between this file and whitenoise_macTests.swift.

Consider folding these test functions into whitenoise_macTests.swift's existing @Suite(.serialized) struct whitenoise_macTests, or adding @Suite(.serialized) here if a separate file is intentional.

As per coding guidelines: "Unit tests in whitenoise-macTests/ should use Swift Testing in a single @Suite(.serialized) struct, and because the runner's per-test -only-testing: filter does not reliably match individual functions in that serialized suite, prefer running the whole suite."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@whitenoise-macTests/PeerDisplayTextTests.swift` around lines 21 - 236, The
new PeerDisplayTextTests suite is outside the repo’s single serialized Swift
Testing suite, which can break test ordering and filtering. Move these tests
into the existing whitenoise_macTests `@Suite`(.serialized) struct, or add
`@Suite`(.serialized) to PeerDisplayTextTests if it must remain separate, and
consolidate shared helpers like isolated, timelineMessage, and groupSystemEvent
to avoid duplication.

Source: Coding guidelines

🧹 Nitpick comments (1)
whitenoise-mac/Models/MessengerModels.swift (1)

2075-2094: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant double sanitization of displayName.

init already stores self.displayName = PeerDisplayText.sanitize(displayName), but title re-sanitizes the already-sanitized value (PeerDisplayText.sanitize(displayName)). Harmless (idempotent) but confusing — the stored property is already clean.

♻️ Proposed simplification
     var title: String {
-        PeerDisplayText.sanitize(displayName) ?? DisplayText.short(accountIdHex)
+        displayName ?? DisplayText.short(accountIdHex)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@whitenoise-mac/Models/MessengerModels.swift` around lines 2075 - 2094, The
title logic is redundantly sanitizing `displayName` even though `init(...)`
already assigns a sanitized value to `self.displayName`. Update `title` in
`MessengerModels` to use the stored `displayName` property directly, falling
back to `DisplayText.short(accountIdHex)` when it is nil, and keep the
sanitization only in the initializer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@whitenoise-macTests/PeerDisplayTextTests.swift`:
- Around line 21-236: The new PeerDisplayTextTests suite is outside the repo’s
single serialized Swift Testing suite, which can break test ordering and
filtering. Move these tests into the existing whitenoise_macTests
`@Suite`(.serialized) struct, or add `@Suite`(.serialized) to PeerDisplayTextTests
if it must remain separate, and consolidate shared helpers like isolated,
timelineMessage, and groupSystemEvent to avoid duplication.

---

Nitpick comments:
In `@whitenoise-mac/Models/MessengerModels.swift`:
- Around line 2075-2094: The title logic is redundantly sanitizing `displayName`
even though `init(...)` already assigns a sanitized value to `self.displayName`.
Update `title` in `MessengerModels` to use the stored `displayName` property
directly, falling back to `DisplayText.short(accountIdHex)` when it is nil, and
keep the sanitization only in the initializer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b3cfc3b-71a3-44e3-a362-9d6903846020

📥 Commits

Reviewing files that changed from the base of the PR and between 74d0ce4 and b61bdeb.

📒 Files selected for processing (10)
  • whitenoise-mac/Core/MarmotMapping.swift
  • whitenoise-mac/Core/PeerDisplayText.swift
  • whitenoise-mac/Core/WorkspaceState+NewChat.swift
  • whitenoise-mac/Core/WorkspaceState+Notifications.swift
  • whitenoise-mac/Core/WorkspaceState.swift
  • whitenoise-mac/Models/MessengerModels.swift
  • whitenoise-mac/Views/GroupViews.swift
  • whitenoise-macTests/PeerDisplayTextTests.swift
  • whitenoise-macTests/PureValueTests.swift
  • whitenoise-macTests/whitenoise_macTests.swift

@agent-p1p
agent-p1p force-pushed the pip/whitenoise-mac-387 branch from 2135be9 to 239cd24 Compare July 10, 2026 11:51
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.

Security/Privacy: peer-controlled display names & group names are rendered with no Unicode bidi-control sanitization (identity spoofing)

2 participants