trustless
HTTPS on registrable domains for local development -- without touching your system trust store.
trustless run rails server
# -> https://my-project.dev.example.com:1443
Trustless infers a subdomain from your project (directory name, package.json, or .trustless.json), allocates an ephemeral port, and starts your app behind a local HTTPS proxy -- all with a single command.
Why
Tools like Portless solve the port-number problem by giving each dev server a stable .localhost URL. But .localhost is not a registrable domain, so:
- Same-site cookies don't work.
a.localhostandb.localhostare independent sites, not subdomains of a common registrable domain, soSameSitecookies can't be shared between services - Secure context requires HTTPS with trusted certificates. Browsers grant secure context to plain
localhost, but once you use a registrable domain, you need a real TLS certificate - Self-signed certs need trust store changes. Installing a local CA means modifying system or browser trust stores on every developer's machine and poses security risks if not handled carefully.
Trustless fixes this by sharing a publicly trusted certificate through a key provider you deploy once, then every developer on the team gets HTTPS on registrable domains with zero local trust store changes. The key provider exposes only a signing interface; private keys are never distributed.
It's noteworthy that sharing a private key is risky! We tolerate by minimizing its risk. By having a key provider in between a locally running HTTPS proxy and actual key materials, we can instantly revoke access when needed.
Install
- Mise:
mise use -g github:sorah/trustless@latest - Binaries: Download from GitHub Releases
These commands install all executables provded in this repository (trustless + provider plugin commands).
How It Works
flowchart TD
Browser["Browser<br>https://my-api.dev.example.com:1443"]
Proxy["trustless proxy<br>(TLS termination, port 1443)"]
ProviderCmd["Provider Command<br>(local CLI)"]
ProviderServer["Provider Server<br>(e.g. Lambda function;<br>signs only, no key export)"]
Storage[("Key Storage<br>(e.g. S3)")]
App1[":4123<br>my-api"]
App2[":4567<br>my-frontend"]
Browser -->|HTTPS| Proxy
Proxy -.->|"sign request → signature"| ProviderCmd
ProviderCmd -.-> ProviderServer
ProviderServer --- Storage
Proxy -->|HTTP| App1
Proxy -->|HTTP| App2
- Key provider -- You deploy a provider (e.g. AWS Lambda + S3) that holds a wildcard certificate for your dev domain. The provider signs TLS handshake data on request but never exports the private key.
- Proxy --
trustless proxyruns locally, terminates TLS using the provider for signing, and forwards plain HTTP to your app on loopback. - Run --
trustless run <command>infers a subdomain, assigns an ephemeral port, registers a route with the proxy, and starts your app withPORTandHOSTset.
Quick Start
1. Set up a key provider
The easiest option for solo use is the Filesystem Provider -- point it at a directory containing your wildcard certificate and key.
For team use with access control and instant revocation, see the AWS Lambda Provider. You can also write your own.
2. Setup DNS
Configure your wildcard domain (e.g. *.dev.example.com) to point to 127.0.0.1 and ::1 via DNS. Trustless does not alter DNS resolution on the machine in any way.
Choose a domain isolated from staging, production, and other in-house tools. Anyone with access to the key provider can sign TLS handshakes for its domains.
3. Configure trustless
Key providers are specified as an arbitrary command line, so you can use any provider implementation.
4. Run your app
&&
# -> https://my-api.dev.example.com:1443
&&
# -> https://my-frontend.dev.example.com:1443
The proxy auto-starts on first use. Start it explicitly with trustless proxy start if you prefer.
Security Notice
[!CAUTION] You are sharing a private key. Anyone with access to the key provider can sign TLS handshakes or any blobs such as JWT -- which means they can impersonate your services. Signing is the most important operation of an asymmetric key, and sharing it is inherently risky.
Trustless reduces abuse risk compared to distributing raw key files by having a key provider concept: Access to signing can be revoked instantly by removing provider access, and the key itself is never exported. But while someone has access, they can fully impersonate.
To limit the blast radius: Use a dedicated domain exclusively for local development (e.g.
*.lo.mycompany-dev.com). You may want to have a isolated registered domain for this purpose, even avoiding subdomains. Never reuse certificates, keys, or domains that are used for other purposes, such as existing TLS private keys or domains serving real traffic.
Routing
Subdomain inference
trustless run infers a subdomain automatically: .trustless.json, package.json (name) in the project, Git repository name, or current directory name.
Explicit subdomain with trustless exec
When you need a specific subdomain rather than the inferred one, use trustless exec:
# -> https://api.dev.example.com:1443
# -> https://web.dev.example.com:1443
When a provider has multiple wildcard domains, the first one is used by default. Use --domain to pick a specific one:
See Routing and Running Apps for full details on domain resolution, port allocation, environment variables, and route lifecycle.
Profiles
Use multiple profiles when you have more than one key provider:
Commands
Aliases: trustless l = list, trustless s = status.
Error Pages & Status
The proxy serves styled HTML error pages with dark mode support:
- 502 Bad Gateway — when the backend is unreachable
- 508 Loop Detected — when a forwarding loop is detected
- 404 Not Found — when no route matches, showing a list of active routes
Visiting https://trustless.<domain>/ in the browser shows a status page with active routes and provider health.
Environment Variables
When using trustless exec or trustless run, the following environment variables are set for the child process:
PORT— the ephemeral port your app should listen onHOST— the hostname to bind to (loopback)TRUSTLESS_URL— the full HTTPS URL for the service (e.g.https://my-app.dev.example.com:1443)
Set TRUSTLESS=0 or TRUSTLESS=skip to bypass the proxy entirely and run the command without routing through trustless.
Framework Detection
trustless exec and trustless run auto-inject framework-specific flags so your dev server listens on the correct host and port:
- Vite / React Router / Astro —
--hostand--port - Angular —
--hostand--port - React Native / Expo —
--port
Prior Art
Heavily inspired by vercel-labs/portless. Portless makes port numbers unnecessary -- hence port-less. Trustless extends the idea to registrable domains over HTTPS, removing the need to trust self-signed certificates or local CAs -- hence trust-less. No trust store modifications required.
State Directory
$XDG_RUNTIME_DIR/trustless or ~/.local/state/trustless