Teitunnel

Guides

Exposure check

Before a share or route makes a local service public, Teitunnel looks at it for common leaks: .env files, the git folder, backups and database dumps, framework debug pages, open admin panels and database tools, Jupyter without a token, source maps. What it finds, how it works, and --strict for scripts and CI.

Sharing a dev server shares everything it serves. Many serve more than the app: the project folder with its .env file, the git history, a database dump someone left there, or a framework's debug pages that print your settings. Before a Quick Share, a share on your domain, a new route or a project's routes go public, Teitunnel looks at the service for these leaks and tells you what it found, with what to do about it.

It never blocks you: you can always share anyway. It only takes a moment, and nothing leaves this computer.

What it looks for

FindingSeverityHow it's recognised
Its .env file can be downloadedHigh/.env answers with NAME=value lines. Only the variable names are shown, never the values.
Its .git folder can be downloadedHigh/.git/config or /.git/HEAD can be downloaded (history, remotes, sometimes credentials).
A backup archive can be downloadedHigh/backup.zip is a zip archive.
A database dump can be downloadedHigh/dump.sql is a MySQL, MariaDB or PostgreSQL dump.
A SQLite database can be downloadedHigh/db.sqlite is a SQLite database.
Laravel's debug tools are onHighIgnition's health check (/_ignition/health-check) or the Whoops error page.
Spring Boot's actuator shows the environmentHigh/actuator/env shows the configuration and environment.
An admin panel opens without a loginHighDjango's admin (/admin/) answers without a login.
A database tool (Adminer or phpMyAdmin) is reachableHigh/adminer.php or /phpmyadmin/ answers.
This port is a database, not a websiteHighThe port speaks MongoDB, Redis, PostgreSQL, MySQL, Elasticsearch or CouchDB.
Jupyter runs without a tokenHighJupyter's API (/api/status) answers without authentication (anyone could run code).
Django runs with DEBUG = TrueMediumThe not-found page lists URL patterns and settings.
Rails runs in development modeMediumThe routing error page with Rails.root, /rails/info/properties, web-console or better_errors.
Symfony's profiler is reachableMedium/_profiler answers.
phpinfo() is reachableMedium/phpinfo.php answers.
Error pages show stack tracesMediumAn error page with a Node/Express, Python or Java stack trace.
A .DS_Store file lists the folder's filesLowmacOS's Finder index is served, revealing file names.
It lists the files in its folderLow/ is a directory listing (nginx and Apache autoindex, Python's http.server, http-server, IIS).
Source maps include your source codeLowA script's .map file includes the original source (usually fine for a demo).

Each finding comes with advice, such as Serve a build folder instead of the project folder, or block dotfiles in the server's settings. Every check looks at the content of the answer, never only its status, so a single-page app that answers every path with its index.html isn't mistaken for a leak.

In the app

  • Quick Share: the check runs when you choose Share (Checking for exposed files…). If it finds something, the findings appear under the form, most serious first, headed localhost:3000 may expose more than you mean to share, with Share Anyway and Cancel.
  • New route: the findings are shown next to the plan (… may expose more than you mean to make public), and Apply is the "anyway". A project's plan shows them for each new route.
  • Menu bar and global shortcut: a one-click share runs the check first. If it finds something, the Quick Share sheet opens with the findings instead of sharing.

If some checks didn't finish in time, the list says Some checks didn't finish in time.

Turn the check off (or on again) in Settings ▸ General ▸ Before Sharing ▸ Check for exposed files and debug pages. The setting applies to the command line too.

From a terminal

teitunnel share, teitunnel route add, teitunnel project apply and teitunnel up print the findings on standard error and carry on:

$ teitunnel share 3000
! http://localhost:3000 may expose more than you mean to share:
  [high] Its .env file can be downloaded (DATABASE_URL, STRIPE_SECRET_KEY) at /.env
         Anyone could read the secrets in it. Serve a build folder instead of the project folder, or block dotfiles in the server's settings.
  [low] Source maps include your source code at /assets/index-4f2a.js.map
         Visitors can read the original code. That's usually fine for a demo; turn off source maps in the build otherwise.
https://quiet-river-1234.trycloudflare.com

With --strict, any finding is an error: nothing is shared or changed, and the command exits with status 1. Use it in scripts and CI:

teitunnel share 3000 --strict
teitunnel route add app.yourhost.com 3000 --strict
teitunnel project apply --strict
teitunnel: Not shared: the exposure check found 2 problem(s) (--strict).

How it works

Teitunnel talks to the service directly, the way it's reached on this computer, not through Cloudflare, so nothing is public while it checks. It makes about twenty GET requests at once (plus one plain request on the port to recognise a database), reads at most 64 KB of each answer, never follows a redirect, and gives up after two seconds in all (1.5 seconds per request), keeping whatever it found by then. Sharing is barely slower, and the service sees a handful of requests for paths like /.env in its log.

It only reads. It never sends credentials, submits forms or tries to log in.

Limits

  • It looks for common leaks at their usual paths. A clean result doesn't prove a service is safe to share: a secret at an unusual path, or an app bug, won't be found.
  • It checks the service as it answers now. A share that serves different content later isn't checked again.
  • For anything sensitive, share on your domain with a login, or use protection.

Troubleshooting

  • A finding you know is harmless. Choose Share Anyway; in the terminal, the warning doesn't stop anything unless you pass --strict.
  • "This port is a database, not a website." Sharing a database over HTTP won't work, and a database shouldn't be public. For remote access, use a TCP route with a login, or a private network.
  • You want no check at all. Turn it off in Settings ▸ General ▸ Before Sharing.
Edit on GitHub

Last updated on

On this page