Skip to content

fix(security): normalize input before dangerous command detection#3260

Merged
teknium1 merged 1 commit intomainfrom
hermes/hermes-5ef8201d
Mar 26, 2026
Merged

fix(security): normalize input before dangerous command detection#3260
teknium1 merged 1 commit intomainfrom
hermes/hermes-5ef8201d

Conversation

@teknium1
Copy link
Copy Markdown
Contributor

Summary

Salvaged from PR #3089 by @thakoreh — cherry-picked onto current main with authorship preserved, improved ANSI stripping, and added comprehensive tests.

Root cause

detect_dangerous_command() ran regex patterns against command.lower() with no normalization. Obfuscation bypasses:

  • Fullwidth Unicode: fullwidth chars normalize to dangerous commands via NFKC
  • ANSI escapes: color codes wrapping command names hide them from regex
  • 8-bit C1 controls: alternative CSI encoding bypasses basic ESC[ matching
  • Null bytes: injected nulls split pattern matches

Fix

Adds _normalize_command_for_detection() that runs before pattern matching:

  1. strip_ansi() from tools/ansi_strip (full ECMA-48 — CSI, OSC, DCS, 8-bit C1, nF sequences)
  2. Null byte removal
  3. NFKC Unicode normalization

Improvement over original PR

The original PR rolled its own ANSI regex (\x1b\[[0-9;]*[a-zA-Z]) which missed private-mode CSI, 8-bit C1 controls, DCS/APC strings, and nF sequences. This version uses our existing comprehensive strip_ansi() which handles all of those.

Validation

Unit tests (12 new)

python -m pytest tests/tools/test_approval.py -n0 -q → 87 passed

Tests cover: fullwidth rm/dd/chmod/mkfs, ANSI CSI/OSC/C1, null bytes, combined obfuscation, and safe command passthrough.

E2E: 21 obfuscation vectors through detect_dangerous_command

All caught, including DCS string prefix, 8-bit C1 CSI, and mixed fullwidth+ANSI combos.

Live PTY: Interactive CLI session

Sent fullwidth dangerous command as user input → model passed it to terminal tool → dangerous command approval dialog triggered with "delete in root path" — confirming the full pipeline works end-to-end.

Full suite

6237 passed, 1 pre-existing failure (unrelated test_429_exhausts_all_retries)

Closes #3089
Fixes #3076

Co-authored-by: Hiren hiren.thakore58@gmail.com

detect_dangerous_command() ran regex patterns against raw command strings
without normalization, allowing bypass via Unicode fullwidth chars,
ANSI escape codes, null bytes, and 8-bit C1 controls.

Adds _normalize_command_for_detection() that:
- Strips ANSI escapes using the full ECMA-48 strip_ansi() from
  tools/ansi_strip (CSI, OSC, DCS, 8-bit C1, nF sequences)
- Removes null bytes
- Normalizes Unicode via NFKC (fullwidth Latin → ASCII, etc.)

Includes 12 regression tests covering fullwidth, ANSI, C1, null byte,
and combined obfuscation bypasses.

Salvaged from PR #3089 by thakoreh — improved ANSI stripping to use
existing comprehensive strip_ansi() instead of a weaker hand-rolled
regex, and added test coverage.

Co-authored-by: Hiren <hiren.thakore58@gmail.com>
@teknium1 teknium1 merged commit 76ed15d into main Mar 26, 2026
1 of 2 checks passed
StreamOfRon pushed a commit to StreamOfRon/hermes-agent that referenced this pull request Mar 29, 2026
…usResearch#3260)

detect_dangerous_command() ran regex patterns against raw command strings
without normalization, allowing bypass via Unicode fullwidth chars,
ANSI escape codes, null bytes, and 8-bit C1 controls.

Adds _normalize_command_for_detection() that:
- Strips ANSI escapes using the full ECMA-48 strip_ansi() from
  tools/ansi_strip (CSI, OSC, DCS, 8-bit C1, nF sequences)
- Removes null bytes
- Normalizes Unicode via NFKC (fullwidth Latin → ASCII, etc.)

Includes 12 regression tests covering fullwidth, ANSI, C1, null byte,
and combined obfuscation bypasses.

Salvaged from PR NousResearch#3089 by thakoreh — improved ANSI stripping to use
existing comprehensive strip_ansi() instead of a weaker hand-rolled
regex, and added test coverage.

Co-authored-by: Hiren <hiren.thakore58@gmail.com>
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.

2 participants