Skip to content

feat: add telnet protocol support#490

Open
billchurch wants to merge 20 commits intomainfrom
feature/telnet-support
Open

feat: add telnet protocol support#490
billchurch wants to merge 20 commits intomainfrom
feature/telnet-support

Conversation

@billchurch
Copy link
Owner

Summary

Adds telnet protocol support to WebSSH2, implementing issue #180:

  • Protocol Service Abstraction: Common ProtocolService interface enabling both SSH and Telnet through the same adapter layer
  • IAC Negotiation Layer: Byte-level state machine for RFC 854/857/858/1073/1091 telnet protocol negotiation (ECHO, SGA, NAWS, TERMINAL_TYPE)
  • Expect-Style Authentication: Configurable regex-based login/password prompt detection with timeout fallback to pass-through mode
  • Connection Pool: Dual-indexed Map for managing telnet connections by connection ID and session ID
  • TelnetServiceImpl: Full ProtocolService implementation with ShellDuplex stream bridging and backpressure support
  • Express Routes: /telnet/ routes mirroring the existing SSH route pattern (GET/POST with host parameter support)
  • Socket.IO Namespace: Separate /telnet/socket.io namespace with protocol-aware adapters
  • Client Updates: SolidJS client updated for protocol-aware UI — hides SFTP/host key features for telnet, shows security warning banner, telnet-specific login modal
  • Security: Disabled by default (telnet.enabled: false), subnet restrictions enforced, regex pattern validation, 64KB auth buffer limit

Architecture

Client (xterm.js) ↔ Socket.IO (/telnet/socket.io) ↔ ServiceSocketAdapter (protocol='telnet')
  ↔ TelnetServiceImpl ↔ ShellDuplex ↔ TelnetNegotiator + TelnetAuthenticator ↔ net.Socket ↔ Telnet Server

New Files

File Purpose
app/services/telnet/telnet-negotiation.ts IAC protocol state machine (ECHO, SGA, NAWS, TERMINAL_TYPE)
app/services/telnet/telnet-auth.ts Expect-style authenticator with configurable patterns
app/services/telnet/telnet-connection-pool.ts Connection lifecycle management
app/services/telnet/telnet-service.ts ProtocolService implementation with ShellDuplex
app/routes/telnet-routes.ts Express route handlers for /telnet/

Configuration

Telnet is configured via config.json under the telnet key or via WEBSSH2_TELNET_* environment variables:

{
  "telnet": {
    "enabled": true,
    "defaultPort": 23,
    "timeout": 30000,
    "term": "vt100",
    "auth": {
      "loginPrompt": "login:\\s*$",
      "passwordPrompt": "[Pp]assword:\\s*$",
      "failurePattern": "Login incorrect|Access denied|Login failed",
      "expectTimeout": 10000
    },
    "allowedSubnets": []
  }
}

Test Plan

  • 79 new unit tests covering all telnet components (negotiation, auth, pool, service, routes, types, config)
  • All 1320 tests passing (1241 existing + 79 new)
  • 0 lint errors, typecheck clean, build succeeds
  • Code review completed with all critical/important findings addressed
  • Manual testing: enable telnet, connect to a telnet server via /telnet/host/<host>
  • Manual testing: verify SSH functionality is unaffected when telnet is disabled
  • Manual testing: verify subnet restrictions block unauthorized hosts

Closes #180

Add foundational telnet types to support telnet protocol alongside SSH:

- TELNET_DEFAULTS constants (port, timeout, term, auth patterns)
- TelnetAuthConfig and TelnetConfig interfaces in config types
- ProtocolType, ProtocolConnection, TelnetConnectionConfig interfaces
- ProtocolService interface (connect, shell, resize, disconnect)
- telnet?: ProtocolService on Services interface
- telnet?: TelnetConfig on Config interface
- Type-level tests verifying constants and interface shapes
Add telnet configuration defaults to DEFAULT_CONFIG_BASE using
TELNET_DEFAULTS constants, with deep clone support via
cloneTelnetConfig(). Wire up WEBSSH2_TELNET_* environment variable
mappings in env-mapper.ts and add TelnetSchema to the Zod validation
schema. Includes unit tests for default values, auth patterns, and
deep cloning behavior.
Implement TelnetNegotiator class that handles telnet IAC option
negotiation, stripping protocol sequences from terminal data and
generating correct responses for DO/WILL/DONT/WONT commands,
NAWS window size, and terminal-type subnegotiations.
Implement TelnetAuthenticator state machine that automates login/password
entry by matching incoming data against configurable prompt patterns.
Falls through to pass-through mode when no auth is configured or on timeout.
Simple connection pool mirroring the SSH ConnectionPool pattern. Stores
TelnetConnection objects (ProtocolConnection + Socket) by ConnectionId
with a secondary session index for efficient lookup by SessionId.
Includes add, get, getBySession, remove, clear, and size operations.
… pool

Implements the full ProtocolService for telnet, wiring together
TelnetNegotiator (IAC handling), TelnetAuthenticator (expect-style
login), and TelnetConnectionPool into connect/shell/resize/disconnect.

Key design decisions:
- ShellDuplex (Duplex subclass) for bidirectional shell stream
- Socket paused in connect(), resumed in shell() to prevent data loss
- Connection metadata map stores original config for deferred auth setup
- createSocketDataHandler extracted as module-level pure function

16 tests covering connect, shell, resize, disconnect, status, and
end-to-end authentication integration.
…tern

- Add protocol parameter to ConnectionOptions in connectionHandler.ts
  to support telnet socket path and config injection
- Update html-transformer to accept basePath parameter for telnet
  asset paths (/telnet/assets/ instead of /ssh/assets/)
- Create telnet-routes.ts with GET /, GET /host/:host, POST /,
  POST /host/:host, and GET /config endpoints
- Mount telnet routes conditionally in app.ts when telnet is enabled
- Add unit tests for route factory, config endpoint, and route structure
…ters

Integrate telnet support into the Socket.IO and service adapter layers:

- Add TelnetService token to DI container (container.ts, setup.ts)
- Create TelnetServiceImpl in factory.ts when telnet is enabled
- Add configureTelnetNamespace() in io.ts for separate /telnet/socket.io path
- Make ServiceSocketAdapter protocol-aware with optional 4th parameter
- Add protocol field to AdapterContext (service-socket-shared.ts)
- Branch auth flow: connectTelnet() uses services.telnet for telnet protocol
- Skip SSH-only features for telnet: host key verification, SFTP, exec,
  keyboard-interactive auth, auth method policy enforcement
- Update terminal handler: use telnet service for shell/resize when protocol
  is telnet
- Update disconnect handler: route to telnet or SSH service based on protocol
- Accept protocol parameter in socket-v2.ts init()
- Bootstrap telnet namespace in app.ts alongside SSH namespace
- Update test contexts to include required protocol field
- Remove duplicate imports in telnet-connection-pool test
- Use Array<T> syntax for complex types in telnet-types test
- Fix negated condition in default-config telnet clone
…ABLES.md

Document the new telnet configuration section covering all options:
enabled, defaultPort, timeout, term, auth prompt patterns, expect
timeout, and allowedSubnets. Includes security warnings about
plaintext transmission, use case examples, and environment variable
reference table.
- Enforce allowedSubnets in TelnetServiceImpl.connect() using
  validateConnectionWithDns (was configured but never checked)
- Add WEBSSH2_TELNET_ALLOWED_SUBNETS env var mapping
- Validate regex patterns in buildOptionalRegex with try/catch
- Add 64KB max buffer limit to TelnetAuthenticator (prevents OOM)
- Replace instant waiting-result heuristic with 500ms settle delay
  to allow failure messages to arrive before declaring success
- Add backpressure to ShellDuplex (pause/resume socket on push)
- Destroy authenticator timers in connection pool clear()
- Remove unused TelnetAuthResult export
Sends WILL TERMINAL-TYPE and WILL NAWS upfront so servers like busybox
telnetd know the client supports terminal-type negotiation. Fixes TERM
showing as 'dumb' on the remote side.

Also fixes a TypeScript strict-mode error in processSubnegotiation()
where array indexing returned number | undefined despite a length guard.
- S7763: use export...from for re-exports in errors.ts,
  middleware/index.ts, exec-handler.ts (9 instances)
- S4325: remove unnecessary type assertion in
  service-socket-adapter test
- S4623: remove redundant undefined args in validation-ssh test
@socket-security
Copy link

socket-security bot commented Feb 27, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedwebssh2_client@​3.4.1 ⏵ 3.5.0-telnet.177 +110099 -196 +1100

View full report

…encies

This macOS-only native binary was accidentally added as a direct
dependency, causing npm ci to fail on Linux CI runners with
EBADPLATFORM. Rollup already manages it as an optional dependency.
@billchurch billchurch mentioned this pull request Mar 1, 2026
Add telnet routes section to ROUTES.md covering all endpoints
(GET/POST /telnet, /telnet/host/:host, /telnet/config). Update
README.md with telnet feature description, enable instructions,
and security warnings. Fix docker-publish workflow to apply
branch-name tags on workflow_dispatch events.
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 1, 2026

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.

Telnet capability?

1 participant