Skip to content

spec: launch LTR as a Bore-compatible TCP tunnel rewrite #1

Description

@fishandsheep

Migrated from fishandsheep/bore#1. This issue is the canonical LTR copy.

Problem Statement

The current repository is a fork of ekzhang/bore. It has accumulated important fork-specific capabilities—an embedded Web control plane, remote Web and SSH bundle modes, npm/Bun distribution, and 11 platform packages—but the product is still presented and packaged as bore. The implementation is also concentrated around the original project shape, which makes the new capabilities harder to evolve, test, secure, and publish as an independent product.

Users need an independently branded TCP tunnelling product that remains compatible with the original Bore protocol and workflows while providing a clean architecture, secure Web administration, first-class npm/Bun installation, and an honest account of its fork/rewrite lineage.

Solution

Create LTR (Local-to-Remote) as a public, independently branded rewrite of the current project.

LTR will keep the core value proposition—exposing local TCP services through a remote server—but reorganise the Rust implementation into a workspace with clear protocol, transport, relay, CLI, and Web control-plane boundaries. It will preserve the existing TCP, HMAC authentication, Web management, SSH bundle, npm/Bun, and multi-platform behaviours where they are part of the agreed product scope.

The new public identity is:

  • GitHub repository: fishandsheep/ltr
  • Binary: ltr
  • Cargo package: ltr-cli
  • npm root package: @qinshower/ltr
  • Initial release: 1.0.0

The project will state clearly that it is an independent fork/rewrite based on ekzhang/bore, retain the MIT licence and attribution materials, and provide a migration path for Bore users.

User Stories

  1. As a developer exposing a local TCP service, I want to run ltr local <port> --to <server>, so that I can keep the existing Bore workflow while using the new product.
  2. As a self-hosting operator, I want to run ltr server, so that I can provide a remote relay for local TCP tunnels.
  3. As a developer, I want full-duplex byte forwarding, so that request and response traffic behaves like a normal TCP connection.
  4. As a developer, I want half-closed TCP connections to continue forwarding in the remaining direction, so that protocols relying on TCP shutdown semantics do not hang.
  5. As a developer, I want client reconnect with backoff and port reuse where possible, so that temporary control-connection failures do not require manual restart.
  6. As a Bore user, I want an old Bore client to connect to an LTR server in the compatible plaintext mode, so that upgrading the server does not force an immediate client migration.
  7. As a Bore user, I want an LTR client to connect to an old Bore server in the compatible plaintext mode, so that I can migrate clients and servers independently.
  8. As an operator, I want the existing control-port and tunnel-port defaults to remain available, so that firewall rules and deployment scripts do not break during migration.
  9. As an operator, I want to configure control and tunnel bind addresses independently, so that I can place control traffic and public tunnel listeners on the intended network interfaces.
  10. As an operator, I want optional HMAC challenge-response authentication, so that I can restrict tunnel clients without sending the shared secret over the wire.
  11. As an operator, I want a clear warning when authentication is not configured, so that an accidentally public unauthenticated relay is visible.
  12. As an operator, I want optional TLS for the whole tunnel connection, so that tunnel payloads can be encrypted when plaintext transport is not acceptable.
  13. As a legacy client, I want the default plaintext protocol to remain available, so that existing clients can continue to connect without TLS support.
  14. As a TLS user, I want the server to validate client certificate trust through a CA or pinned fingerprint, so that enabling TLS does not require insecure certificate bypasses.
  15. As a local developer, I want to start a Web control plane bound to loopback by default, so that local tunnel administration does not become public accidentally.
  16. As a Web user, I want to create, start, stop, delete, inspect, and read logs for local tunnels, so that common tunnel operations do not require repeatedly reconstructing CLI arguments.
  17. As a Web user, I want stable tunnel status and error states, so that I can tell whether a tunnel is starting, running, stopped, or failed.
  18. As a Web user, I want bounded logs and resource limits, so that a long-running control plane remains responsive and does not grow without limit.
  19. As a remote Web user, I want the Web service to use HTTPS before traffic enters the tunnel, so that browser-to-local-control-plane traffic remains encrypted end to end.
  20. As a first-time remote Web user, I want LTR to generate and persist a usable self-signed certificate automatically, so that I can start remote administration without first learning certificate tooling.
  21. As an operator with a real domain or internal PKI, I want to provide PEM certificate and private-key files, so that the automatic certificate can be replaced in production.
  22. As a remote Web user, I want a separate Web bearer token, so that browser administration credentials are not coupled to the tunnel Secret.
  23. As a remote Web user, I want a login field that stores the token in session storage and never places it in the URL, so that browser history and copied links do not disclose the token.
  24. As a local loopback user, I want local Web administration to work without a login by default, so that the common local workflow remains low friction.
  25. As a remote Web user, I want every Web API endpoint to enforce the bearer token, so that read operations cannot leak tunnel state and write operations cannot be forged.
  26. As an operator, I want remote Web mode to require explicit public exposure configuration, so that a local Web command does not silently publish an administrative surface.
  27. As a developer, I want the Web API v1 session, tunnel lifecycle, and log contracts to remain stable, so that the embedded frontend and future clients can evolve independently.
  28. As an operator, I want the Web control plane to support a combined Web-plus-SSH mode, so that I can expose a browser console and SSH endpoint as one managed bundle.
  29. As a Bore user, I want bore home to migrate to ltr bundle, so that the existing combined workflow remains usable during the transition.
  30. As a CLI user, I want bore to remain available as a deprecated compatibility binary for two LTR major versions, so that scripts can be migrated deliberately.
  31. As an npm user, I want @qinshower/ltr to select the correct prebuilt platform package automatically, so that installation does not require manual binary downloads.
  32. As a Bun user, I want bunx @qinshower/ltr to work with the same platform selection model, so that Bun is a first-class distribution path.
  33. As an existing npm user, I want @qinshower/bore and its platform packages to remain deprecated wrappers for two LTR major versions, so that existing package manifests do not fail immediately.
  34. As a Cargo user, I want cargo install ltr-cli to install the new binary, so that Rust users have a native installation path.
  35. As an existing Cargo user, I want bore-cli to remain a deprecated wrapper for two LTR major versions, so that Cargo-based automation has a migration window.
  36. As a user on a supported platform, I want the launcher to fail fast with a useful supported-platform list when no package exists, so that unsupported environments are diagnosable.
  37. As a release consumer, I want standalone binaries and SHA-256 checksums, so that I can install without npm and verify downloaded artifacts.
  38. As a maintainer, I want one Git tag to build Rust artifacts, create a GitHub Release, publish npm packages, and run smoke tests, so that releases are repeatable.
  39. As a maintainer, I want CI to exercise old Bore and new LTR interoperability in both directions, so that compatibility claims are continuously checked.
  40. As a maintainer, I want CI to smoke-test all supported npm platform packages, so that a successful Rust build does not hide a broken package launcher.
  41. As a maintainer, I want human-readable stderr logs with configurable levels and optional JSON output, so that local debugging and automated log collection both work.
  42. As a security-conscious user, I want secrets and tokens excluded from logs and error messages, so that normal diagnostics do not become credential leaks.
  43. As a project contributor, I want an English-first README with Chinese quick-start and migration material, so that the project is discoverable internationally while remaining accessible to current Chinese users.
  44. As a project contributor, I want the README and attribution documents to explain the Bore lineage and LTR-specific additions, so that the relationship to the upstream project is transparent.
  45. As a security reporter, I want a SECURITY document with threat model, secure deployment guidance, and a vulnerability-reporting route, so that security issues have a documented handling path.
  46. As a user, I want no telemetry or analytics collected by default, so that the tunnel remains a self-contained local/self-hosted tool.
  47. As a maintainer, I want the implementation staged from protocol and compatibility tests through Server/Client, Web, packaging, and documentation, so that each migration step has a verifiable external behaviour boundary.

Implementation Decisions

Product identity and migration

  • The new canonical brand is LTR, expanded as Local-to-Remote.
  • The repository is a public independent project at fishandsheep/ltr, subject to a final availability check before creation.
  • The first LTR release is 1.0.0.
  • The canonical binary is ltr; the canonical Cargo package is ltr-cli; the canonical npm package is @qinshower/ltr.
  • The old bore binary and command surface remain as deprecated compatibility entry points for two LTR major versions. They emit migration warnings without changing forwarded arguments.
  • ltr local, ltr server, and ltr web remain the primary commands. The combined Web-plus-SSH mode is renamed to ltr bundle; bore home maps to it as a deprecated compatibility command.
  • Canonical environment variables use the LTR_* prefix. Existing BORE_* variables remain accepted with deprecation warnings. Precedence is CLI arguments, then LTR_*, then BORE_*.
  • Existing user-facing defaults, including the control port, Web port, SSH bundle port, and tunnel port range, remain compatible and can still be overridden.

Architecture

  • Replace the current single-package shape with a Rust workspace organised around deep seams: protocol and framing, authentication, transport/TLS, relay/session lifecycle, CLI, Web control plane, and distribution-facing launch behaviour.
  • Keep the public Rust API intentionally small; expose stable domain types and orchestration boundaries rather than leaking framing or task-management details into every module.
  • Preserve Tokio-based asynchronous I/O and Rust 2021 idioms.
  • Rebuild the implementation in vertical slices while preserving the current working tree changes as input; do not overwrite unrelated user modifications.
  • Keep the embedded Web frontend as a static asset bundle, but rewrite its state/update boundaries and API client. Do not introduce a large frontend runtime or a separate Web deployment requirement.
  • Continue to model the Web product as a local control plane for user tunnels and system tunnels. Preserve the existing product/design vocabulary and the quiet, state-first visual language described by the repository’s product and design documents.

Protocol and data plane

  • TCP remains the only tunnel transport in scope. UDP, QUIC, VPN functionality, compression, and application-aware proxying are out of scope.
  • Protocol v1 remains frozen for the compatible plaintext path. Existing Bore and new LTR clients and servers must interoperate in both directions.
  • Future incompatible protocol changes use explicit version negotiation rather than silently changing v1 frames.
  • The relay remains a full-duplex byte stream. It must preserve half-close behaviour, backpressure, bounded frames, keepalive, connection cleanup, and reconnect semantics.
  • The control connection continues to allocate or honour requested remote ports within the configured range and reports the assigned port to the client.
  • Optional tunnel TLS wraps the control and data path. Plaintext remains the default for v1 compatibility. The server auto-detects the supported plaintext/TLS connection mode on the shared control port.
  • TLS certificate configuration supports automatic generation/persistence and explicit PEM certificate/private-key material. Clients validate through configured CA material or a pinned fingerprint; insecure verification bypass is not the default.

Authentication and security

  • HMAC challenge-response authentication remains available and does not transmit the shared Secret.
  • Secret configuration remains optional for compatibility. A server without a Secret emits a clear security warning and never logs the Secret.
  • Web authentication is separate from tunnel authentication.
  • Local Web mode binds to loopback and does not require a Web login by default.
  • Remote Web mode requires explicit public exposure configuration, HTTPS, and a separate bearer token. Static assets may be served before authentication, but every Web API request requires the token.
  • Remote Web tokens are generated and persisted in the user data directory unless explicitly supplied through a CLI option or environment variable. The browser login stores the token in session storage and never puts it in a URL.
  • Remote Web HTTPS terminates in the local Web process before the connection is forwarded through the tunnel. The remote relay does not need to terminate Web TLS.
  • Automatic Web certificates are persisted and can be replaced with PEM material. Built-in ACME is out of scope.
  • Default configuration is CLI and environment variables only. Generated certificates and Web tokens are the only v1 security state persisted outside memory.
  • No telemetry, analytics, or hidden network calls are introduced.

Web control plane

  • Preserve Web API v1 session metadata, tunnel CRUD/lifecycle operations, system-tunnel locking, status, and bounded logs.
  • Keep Web state in memory. Tunnel definitions are not restored automatically after process restart.
  • Preserve remote Web and SSH system-tunnel behaviours, including loopback-only restrictions for remote management targets.
  • Add explicit resource limits for tunnel count, active connections, and retained log lines; expose adjustments through documented configuration.
  • Preserve accessible, state-first UI behaviour: stable DOM identity during polling, visible text for error states, live-region feedback, keyboard-usable controls, readable logs, and responsive desktop/mobile layout.
  • Web API tests target HTTP request/response behaviour and authorization contracts rather than internal state representation.

Distribution

  • Keep the current 11 platform package families and rename their canonical package/binary identity to LTR.
  • The npm launcher remains a pure JavaScript platform selector and process launcher. It supports current Node.js LTS and Bun 1.x without requiring a post-install network download or a source build.
  • Publish the new LTR root and platform packages. Publish old Bore packages as deprecated wrappers for two LTR major versions.
  • Publish ltr-cli to crates.io and retain bore-cli as a deprecated wrapper for the same migration window.
  • Publish standalone Release binaries with SHA-256 checksums.
  • Docker support is explicitly out of scope and should not be carried into the new distribution surface.
  • Release automation is tag-driven and must build, package, smoke-test, checksum, and publish in a deterministic order.

Documentation and governance

  • Make the README English-first with Chinese quick-start and migration sections.
  • State clearly that LTR is an independent fork/rewrite based on ekzhang/bore, link to the upstream project, describe retained upstream behaviour, and enumerate LTR additions.
  • Preserve the MIT licence and add explicit notice/acknowledgement material.
  • Add security guidance covering plaintext defaults, HMAC authentication, tunnel TLS, Web HTTPS, Web bearer tokens, bind addresses, and certificate handling.
  • Add a SECURITY document and a vulnerability-reporting workflow.
  • Do not create a Docker quick start or Docker release contract.

Testing Decisions

  • The highest testing seam is the external executable/network behaviour boundary: start a server and client, drive TCP traffic through the assigned tunnel, and assert observable connection, data, authentication, reconnect, shutdown, and error behaviour.
  • Existing authentication, end-to-end TCP, and Web API integration suites are the prior art. Keep lower-level framing and authentication tests only where they protect a stable protocol contract or a meaningful resource boundary.
  • Tests should assert external behaviour—wire compatibility, HTTP responses, CLI exit/output contracts, port allocation, bytes forwarded, and lifecycle state—not private task structure, concrete module layout, or implementation-specific scheduling.
  • The compatibility matrix must cover old Bore client to new LTR server, new LTR client to old Bore server, and new LTR client/server pairs in plaintext mode.
  • Data-plane tests must cover unauthenticated compatibility, matching and mismatched HMAC Secrets, bidirectional traffic, half-close, reconnect after control EOF or invalid frames, invalid addresses, bounded frame rejection, stale connection cleanup, requested and allocated ports, and resource-limit errors.
  • TLS tests must cover opt-in negotiation on the shared control port, certificate/CA or fingerprint validation, rejected invalid certificates, and preservation of the plaintext default.
  • Web tests must cover local loopback defaults, remote exposure restrictions, Web HTTPS startup, automatic and PEM certificates, bearer-token enforcement for all API routes, login/session behaviour, tunnel CRUD/lifecycle, system-tunnel locking, logs, bounded resources, and graceful shutdown.
  • CLI tests must cover canonical commands, deprecated Bore aliases, argument precedence, LTR_* variables, BORE_* migration variables, warnings, help/version output, and stable failures.
  • npm/Bun tests must pack the root and platform packages, verify platform selection, execute the launcher on every supported package family, fail clearly for unsupported platforms, and verify the old Bore wrappers.
  • Release tests must validate Cargo artifacts, GitHub Release assets, SHA-256 checksums, npm dry runs, and the tag-driven publication order.
  • CI must run the old/new compatibility matrix and all package smoke tests rather than only compiling the new implementation.

Out of Scope

  • UDP, QUIC, VPN, peer-to-peer mesh, multiplexed non-TCP protocols, compression, or application-aware proxying.
  • Mandatory TLS for all traffic or removal of the plaintext compatibility path.
  • Multi-user Web accounts, RBAC, team management, or remote identity providers.
  • Persistent tunnel configuration, automatic tunnel restoration, databases, or external state stores.
  • Built-in ACME certificate issuance and renewal.
  • Docker images, Docker Compose, or Docker-specific release contracts.
  • Source-compilation fallback in the npm launcher.
  • Automatic binary self-update.
  • Telemetry, analytics, crash reporting, or hidden outbound reporting.
  • A breaking Web API redesign in v1.
  • Removing Bore compatibility before the two-major-version migration window.
  • Creating a new GitHub repository under a different name without an explicit follow-up decision if fishandsheep/ltr is unavailable.

Further Notes

  • The current repository contains uncommitted user changes in the Rust manifest, client/server/shared code, and end-to-end tests. Implementation must preserve and review those changes rather than resetting or overwriting them.
  • The current upstream relationship is part of the product story, not an implementation secret. The new project should distinguish its independent brand while making lineage and licensing easy to verify.
  • The first implementation stage should establish the compatibility harness and protocol seam before replacing the current Server/Client internals. This gives the rewrite a measurable behavioural target and protects the npm/Web work from becoming the only tested surface.
  • Formal GitHub, npm, and Cargo name availability should be checked immediately before publishing the new project identity. If any name is unavailable, stop and ask rather than silently selecting a different brand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified work ready for agent implementation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions