Skip to content

Reject control characters in the USER ident - #123

Merged
codegeek merged 2 commits into
mainfrom
fix/issue-121-user-ident-controls
Oct 28, 2026
Merged

codegeek merged 2 commits into
mainfrom
fix/issue-121-user-ident-controls

Conversation

@codegeek

@codegeek codegeek commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #121.

Why

The ident appears in every nick!ident@host prefix the server sends: JOIN, PRIVMSG and NOTICE sources, WHO, WHOIS, and NAMES with userhost-in-names. USER only rejected NUL, CR, LF, space and @. Every other control character got through: TAB, the CTCP delimiter \x01, the formatting codes \x02, \x03 and so on, DEL, and C1 controls such as U+0085 (NEL). A client could therefore plant line breaks, CTCP framing or colors inside its hostmask as other users see it. Sourcery found this while reviewing #120.

What

  • Hostmask.isValidUsernameContent now rejects any ISO control character (Character.isISOControl: C0, DEL and C1). Space and @ are still rejected. UserCommandHandler already falls back to the ident user for invalid content, and that hasn't changed.
  • Ident truncation to IDENT_MAX_CHARS (9 chars) no longer splits a surrogate pair. Before, abcdefgh😀 became abcdefgh plus a lone surrogate, which goes out as ?. Now it becomes abcdefgh.
  • The contract text in specs/001-ircv3-server/contracts/irc-protocol-commands.md now matches.

Formatting codes in message text, the realname, topics and reasons are untouched. #122 tracks a channel mode for those.

Validation

  • HostmaskUsernameContentTest checks that TAB, \x01, \x1f, DEL, U+0080, U+0085, U+009F, NUL, CR, LF, space and @ are rejected. It also checks that alice, ~ident, 界界 and café are accepted, and that null and empty input are handled.
  • UserIdentControlCharacterTest is a socket test. It sends idents containing \x01, U+0085, TAB and DEL, and checks that JOIN, WHOIS 311 and WHO 352 show user. A second case shows the surrogate-safe cut.
  • All of the new tests failed before the fix.
  • ./gradlew cleanTest build sonar passes, and the local SonarCloud branch scan passed the quality gate with 0 issues.
  • Codex reviewed the diff against .github/REVIEW_CHECKLIST.md and found nothing. It also confirmed that UserCommandHandler is the only production caller of setIdent.

🤖 Generated with Claude Code

Summary by Sourcery

Harden USER ident validation and display handling against control characters and malformed Unicode.

Bug Fixes:

  • Reject ISO control characters in USER idents so they cannot appear in client-visible hostmasks.
  • Prevent ident truncation from splitting surrogate pairs and producing malformed output.

Documentation:

  • Update the IRC protocol contract and research documentation to describe the expanded username validation and surrogate-safe ident handling.

Tests:

  • Add protocol and integration coverage for control-character rejection, fallback idents, and safe truncation.

The ident appears in every nick!ident@host prefix (JOIN, message
sources, WHO, WHOIS, NAMES with userhost-in-names), but USER only
rejected NUL, CR, LF, space and @. TAB, the CTCP delimiter \x01,
formatting codes, DEL and C1 controls such as U+0085 (NEL) passed, so a
client could forge line breaks, CTCP framing or colors inside other
users' views of its hostmask. Any ISO control now falls back to the
ident "user", like the other invalid content.

Truncating the ident to its 9-char limit could also cut a surrogate
pair in half, leaving a lone surrogate that goes out as '?'; the cut
now steps back before a high surrogate.

fixes #121

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR hardens USER ident handling by rejecting all ISO control characters before they can appear in hostmasks, retains the existing user fallback, safely truncates long idents without splitting surrogate pairs, and adds unit/integration coverage plus matching protocol documentation.

Sequence diagram for safe USER ident handling

sequenceDiagram
    participant Client
    participant UserCommandHandler
    participant Hostmask
    participant ClientSession
    participant IRCResponse

    Client->>UserCommandHandler: handle(USER)
    UserCommandHandler->>Hostmask: isValidUsernameContent(username)
    alt invalid username content
        UserCommandHandler->>ClientSession: setIdent(user)
    else valid username content
        UserCommandHandler->>ClientSession: setIdent(username)
    end
    UserCommandHandler->>ClientSession: setRealname(realname)
    ClientSession->>IRCResponse: render hostmask ident
    IRCResponse-->>Client: JOIN, WHOIS 311, or WHO 352
Loading

Flow diagram for surrogate-safe ident truncation

flowchart LR
    A[USER ident] --> B{Length exceeds IDENT_MAX_CHARS?}
    B -- No --> C["setIdent(ident)"]
    B -- Yes --> D{Character at truncation boundary is high surrogate?}
    D -- Yes --> E[Remove boundary high surrogate]
    D -- No --> F[Keep IDENT_MAX_CHARS boundary]
    E --> C
    F --> C
Loading

File-Level Changes

Change Details Files
Reject ISO control characters from USER idents before storing the session ident.
  • Expanded username validation to reject C0, DEL, and C1 controls while retaining space and @ checks.
  • Preserved existing fallback to user for invalid ident content.
  • Added unit coverage for rejected controls, delimiters, null/empty input, and accepted Unicode/non-control content.
jircd-core/src/main/java/net/jircd/core/session/command/UserCommandHandler.java
jircd-protocol/src/main/java/net/jircd/protocol/Hostmask.java
jircd-protocol/src/test/java/net/jircd/protocol/HostmaskUsernameContentTest.java
Make ident length truncation safe for UTF-16 surrogate pairs.
  • Avoided cutting immediately after a high surrogate at the nine-character display limit.
  • Added an integration test verifying emoji truncation produces the valid prefix without replacement characters.
jircd-core/src/main/java/net/jircd/core/session/command/UserCommandHandler.java
jircd-integration-tests/src/test/java/net/jircd/integration/UserIdentControlCharacterTest.java
Verify sanitized idents across protocol responses and align the protocol contract.
  • Added socket coverage for control-character idents in JOIN, WHOIS 311, and WHO 352 output.
  • Updated the USER contract to describe ISO control-character rejection and the nine-character display limit.
jircd-integration-tests/src/test/java/net/jircd/integration/UserIdentControlCharacterTest.java
specs/001-ircv3-server/contracts/irc-protocol-commands.md

Assessment against linked issues

Issue Objective Addressed Explanation
#121 Reject USER idents containing any ISO control character and fall back to the safe ident user, preventing those characters from appearing in server-generated identity prefixes. ✅
#121 Verify the rejection through registration and confirm the unsafe ident is not echoed in JOIN, WHO, or WHOIS responses, while retaining support for valid idents. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Oct 28, 2026

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="specs/001-ircv3-server/contracts/irc-protocol-commands.md" line_range="139" />
<code_context>
   ever stating.
 - **Username** (the `<user>` parameter of `USER`, RFC 2812 §2.3.1's
-  `user` production): any octet except NUL, CR, LF, space, and `@` — no
+  `user` production): any character except space, `@`, and ISO control characters (C0 including NUL, CR, LF, TAB and \x01, DEL, and C1) — no
   length limit specified by the RFC itself, but this server MUST apply
   the same 9-character limit as `Hostmask`'s `ident` field for display
</code_context>
<issue_to_address>
**Research gives stale username rule**

When a reader relies on the research document for the USER username rule, `research.md` states the old octet rule while `Hostmask.isValidUsernameContent` rejects TAB, CTCP and C1 characters and `UserCommandHandler` substitutes `user`, so readers expect usernames the server rejects.

Update the registration grammar in `research.md` to match the username rule in the contract and validator.
</issue_to_address>

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Refs #121

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

@sourcery-ai sourcery-ai 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.

Sourcery assessment

Approved.

@sonarqubecloud

Copy link
Copy Markdown

@codegeek
codegeek merged commit def6114 into main Oct 28, 2026
6 checks passed
@codegeek
codegeek deleted the fix/issue-121-user-ident-controls branch October 28, 2026 02:33
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.

[Bug] USER accepts control characters in the ident

1 participant