Skip to content

sec: Prevent ANSI Escape Control Code Injection in Input Fields (#3201) - #3280

Open
knoxiboy wants to merge 2 commits into
Karanjot786:mainfrom
knoxiboy:3201-sec-prevent-ansi-escape-injection
Open

sec: Prevent ANSI Escape Control Code Injection in Input Fields (#3201)#3280
knoxiboy wants to merge 2 commits into
Karanjot786:mainfrom
knoxiboy:3201-sec-prevent-ansi-escape-injection

Conversation

@knoxiboy

@knoxiboy knoxiboy commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

This PR prevents ANSI escape control code injection in TextInput (@termuijs/widgets) and MultilineTextInput (@termuijs/ui) by sanitizing user input and value assignments using stripAnsiControl when raw: false (default), while preserving formatting when raw: true.

Related Issue

Closes #3201

Which package(s)?

@termuijs/widgets, @termuijs/ui, @termuijs/core

Type of Change

  • 🐛 Bug fix ( ype:bug)
  • ✨ Feature ( ype:feature)
  • 📝 Docs ( ype:docs)
  • 🧪 Tests ( ype:testing)
  • ♻️ Refactor ( ype:refactor)
  • 🎨 Design / UX ( ype:design)
  • ♿ Accessibility ( ype:accessibility)
  • ⚡ Performance ( ype:performance)
  • 🔧 DevOps / CI ( ype:devops)
  • 🔒 Security ( ype:security)

Checklist

  • ⭐ You starred the repo. The
    eeds-star check blocks your merge otherwise.
  • Tests pass locally: �un vitest run
  • Build passes: �un run build
  • Typecheck passes: �un run typecheck
  • You read CONTRIBUTING.md.
  • Your PR title follows ype: short description.
  • Widget state mutators call markDirty() (if your change affects rendering).
  • No new �ny types without an inline comment explaining why.
  • No unrelated refactors bundled into this PR.

GSSoC 2026 Participation

Notes for the Reviewer

Added security sanitization via stripAnsiControl in TextInput and MultilineTextInput while preserving raw: true options and fixing OSC sequence regex matching in @termuijs/core.

Summary by CodeRabbit

  • Security

    • Improved sanitization of ANSI escape sequences and control characters in text inputs.
    • Text and inserted characters are cleaned by default before being processed.
    • Added support for preserving raw ANSI sequences when the raw: true option is enabled.
  • Bug Fixes

    • Prevented invisible or potentially disruptive control sequences from entering input values while preserving normal text and line breaks.

@knoxiboy
knoxiboy requested a review from Karanjot786 as a code owner July 29, 2026 12:59
Copilot AI review requested due to automatic review settings July 29, 2026 12:59

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added area:widgets @termuijs/widgets area:core @termuijs/core area:ui @termuijs/ui type:testing +10 pts. Tests. labels Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 53 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 60f06688-6130-4ce5-9184-69b586109aca

📥 Commits

Reviewing files that changed from the base of the PR and between 928ce35 and 40f592d.

📒 Files selected for processing (2)
  • packages/core/src/utils/ansi.test.ts
  • packages/core/src/utils/ansi.ts
📝 Walkthrough

Walkthrough

stripAnsiControl is updated and applied to TextInput and MultilineTextInput values and character insertions. TextInput preserves escape sequences in raw mode. Tests cover sanitization and raw-mode behavior.

Changes

ANSI input sanitization

Layer / File(s) Summary
TextInput sanitization
packages/core/src/utils/ansi.ts, packages/widgets/src/input/TextInput.ts, packages/widgets/src/input/TextInput.test.ts
The ANSI stripping regex is updated, and TextInput sanitizes initial values, assigned values, and inserted characters unless raw: true is enabled.
MultilineTextInput sanitization
packages/ui/src/MultilineTextInput.ts, packages/ui/src/MultilineTextInput.test.ts
MultilineTextInput sanitizes assigned multiline values and inserted characters before updating internal lines, with regression tests for both paths.

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

Possibly related PRs

Suggested labels: quality:clean, level:advanced, type:security

Suggested reviewers: copilot, karanjot786

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the security-focused ANSI injection fix.
Description check ✅ Passed The description includes the required sections and clearly states the change, issue, packages, type, checklist, and reviewer notes.
Linked Issues check ✅ Passed The PR sanitizes ANSI input in TextInput and MultilineTextInput, updates the core stripping utility, and adds security tests, matching the issue's main goal.
Out of Scope Changes check ✅ Passed The core regex tweak and test additions are directly tied to the ANSI sanitization work, with no unrelated changes evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/widgets/src/input/TextInput.ts (1)

142-154: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Account for all graphemes left after sanitization.

stripAnsiControl() can turn a hostile string into multiple printable graphemes, but insertChar() inserts that result as one array element and the following cursor increment advances only once. For example, inserting '\x1b[31mAB\x1b[0m' stores AB, leaves the cursor between A and B, and can bypass _maxLength.

Split sanitizedChar into graphemes, truncate to the available capacity, insert them with spread syntax, and advance _cursorPos by the number actually inserted.

🤖 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 `@packages/widgets/src/input/TextInput.ts` around lines 142 - 154, Update
insertChar around sanitizedChar and the graphemes insertion so the sanitized
value is split into individual graphemes before insertion. Respect the remaining
_maxLength capacity by truncating the sanitized graphemes, insert the retained
graphemes as separate elements, and advance _cursorPos by the number actually
inserted rather than once.
🧹 Nitpick comments (1)
packages/widgets/src/input/TextInput.test.ts (1)

448-464: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the setter and raw mutation paths.

This suite does not exercise the changed TextInput.value setter, nor raw-mode assignment or insertChar() behavior. Add direct setter assertions for both sanitized and raw values so regressions in Lines 95-100 and the raw branch cannot pass unnoticed.

🤖 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 `@packages/widgets/src/input/TextInput.test.ts` around lines 448 - 464, The
ANSI sanitization tests only cover constructor initialization and miss the
changed TextInput.value setter and raw-mode mutation paths. Extend the “ANSI
Control Code Security Sanitization” suite with direct value assignments that
verify control codes are stripped when raw is false, preserved when raw is true,
and that raw-mode insertChar() retains its input.
🤖 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.

Inline comments:
In `@packages/core/src/utils/ansi.ts`:
- Around line 231-234: The ANSI sanitizer regex in stripAnsiControl must
prioritize complete DCS/SS2/SS3 sequences before the single-byte ESC fallback,
so payloads are removed along with their introducers. Reorder the alternatives
accordingly and broaden the CSI final-byte range from [a-zA-Z] to [`@-`~],
preserving the existing handling of other control sequences.

---

Outside diff comments:
In `@packages/widgets/src/input/TextInput.ts`:
- Around line 142-154: Update insertChar around sanitizedChar and the graphemes
insertion so the sanitized value is split into individual graphemes before
insertion. Respect the remaining _maxLength capacity by truncating the sanitized
graphemes, insert the retained graphemes as separate elements, and advance
_cursorPos by the number actually inserted rather than once.

---

Nitpick comments:
In `@packages/widgets/src/input/TextInput.test.ts`:
- Around line 448-464: The ANSI sanitization tests only cover constructor
initialization and miss the changed TextInput.value setter and raw-mode mutation
paths. Extend the “ANSI Control Code Security Sanitization” suite with direct
value assignments that verify control codes are stripped when raw is false,
preserved when raw is true, and that raw-mode insertChar() retains its input.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 103e60b7-1a11-4c41-ae1c-461de262dc49

📥 Commits

Reviewing files that changed from the base of the PR and between 48f63a1 and 928ce35.

📒 Files selected for processing (5)
  • packages/core/src/utils/ansi.ts
  • packages/ui/src/MultilineTextInput.test.ts
  • packages/ui/src/MultilineTextInput.ts
  • packages/widgets/src/input/TextInput.test.ts
  • packages/widgets/src/input/TextInput.ts

Comment thread packages/core/src/utils/ansi.ts Outdated
Karanjot786#3201)

- Fix alternative ordering in the regex: move DCS/PM/APC [PX^_] handler BEFORE
  the catch-all [@-Z\_] so that ESC P/X/^/_ is consumed by the specific handler
  (stripping the full payload), not by the catch-all (which left payload in output)
- Broaden CSI final-byte range from [a-zA-Z] to [@-~] per ECMA-48 to cover
  sequences like ESC[2~ (Insert), ESC[15~ (F5) with ~ final byte
- Add explicit SS2/SS3 (ESC N / ESC O) matching via [NO]. alternative
- Fix incorrect test expectations: OSC and DCS should produce '' not partial payload
- Add regression tests for DCS/PM/APC payload stripping, SS2/SS3, and CSI ~

Addresses security review comment from coderabbitai
@github-actions

Copy link
Copy Markdown

No activity on this PR for 14 days. Rebase, resolve conflicts, or comment to keep it open. It closes in 7 days otherwise.

@github-actions github-actions Bot added the stale No activity in 14 days. label Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core @termuijs/core area:ui @termuijs/ui area:widgets @termuijs/widgets stale No activity in 14 days. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security]: Prevent ANSI Escape Control Code Injection in Input Fields

2 participants