Skip to content

Commit 0d4b287

Browse files
dorlugasigalCopilot
andcommitted
fix(test): use real PNG magic bytes in upload serve test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ad3c247 commit 0d4b287

4 files changed

Lines changed: 228 additions & 11 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ Environment variables: `PORT`, `TERMBEAM_PASSWORD`, `TERMBEAM_CWD`, `TERMBEAM_LO
128128

129129
TermBeam auto-generates a password and creates a tunnel by default, so your terminal is protected out of the box. By default, the server binds to `127.0.0.1` (localhost only). Use `--lan` or `--host 0.0.0.0` to allow LAN access, or `--no-tunnel` to disable the tunnel.
130130

131-
Auth uses secure httpOnly cookies with 24-hour expiry, login is rate-limited to 5 attempts per minute, and security headers (X-Frame-Options, X-Content-Type-Options, etc.) are set on all responses. The QR code on startup embeds a share token for password-free login — the token is reusable within its 5-minute validity window, which handles tunnel proxy retries and link preview services. API clients that can't use cookies can authenticate with an `Authorization: Bearer <password>` header. See the [Security Guide](https://dorlugasigal.github.io/TermBeam/security/) for more.
131+
Auth uses secure httpOnly cookies with 24-hour expiry, login is rate-limited to 5 attempts per minute, and security headers (X-Frame-Options, X-Content-Type-Options, etc.) are set on all responses. The QR code on startup embeds a share token for password-free login — the token is reusable within its 5-minute validity window, which handles tunnel proxy retries and link preview services. API clients that can't use cookies can authenticate with an `Authorization: Bearer <password>` header.
132+
133+
For the full threat model, safe usage guidance, and a quick safety checklist, see [SECURITY.md](SECURITY.md). For detailed security feature documentation, see the [Security Guide](https://dorlugasigal.github.io/TermBeam/security/).
132134

133135
## Contributing
134136

SECURITY.md

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,124 @@
99

1010
We only support the latest published version. Please upgrade before reporting issues.
1111

12+
## Threat Model
13+
14+
TermBeam exposes a real shell over the network. The risk depends entirely on **how** you run it. Understanding the operating modes below helps you make informed decisions.
15+
16+
### Operating Modes
17+
18+
TermBeam has three access layers that combine to determine your risk profile:
19+
20+
| Mode | Command | Who Can Connect | Auth | Risk |
21+
| ----------------------------- | ------------------------------------------ | ------------------------------------- | --------------------------------- | --------- |
22+
| **Default (private tunnel)** | `termbeam` | Only you (Microsoft login + password) | Auto-password + tunnel owner auth | ✅ Low |
23+
| **Public tunnel** | `termbeam --public` | Anyone with the URL + password | Auto-password | ⚠️ Medium |
24+
| **LAN-only (localhost)** | `termbeam --no-tunnel` | Local machine only | Auto-password | ✅ Low |
25+
| **LAN-only (all interfaces)** | `termbeam --no-tunnel --lan` | Any device on your network | Auto-password | ⚠️ Medium |
26+
| **Localhost, no password** | `termbeam --no-tunnel --no-password` | Local processes only | None | ⚠️ Medium |
27+
| **LAN, no password** | `termbeam --no-tunnel --no-password --lan` | Anyone on your network | None | 🔴 High |
28+
29+
> **Note:** `--public --no-password` is rejected by the CLI — TermBeam refuses to start with a public tunnel and no password.
30+
31+
### Private Tunnel (Default)
32+
33+
The default mode creates an ephemeral [Azure DevTunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/) that requires **two layers of authentication**:
34+
35+
1. **Microsoft account login** — only the tunnel owner can access the URL
36+
2. **TermBeam password** — auto-generated on each run
37+
38+
This is the safest way to access your terminal remotely. The tunnel URL is HTTPS, the connection is encrypted end-to-end, and the URL is unguessable.
39+
40+
### Public Tunnel
41+
42+
With `--public`, the tunnel URL is accessible to anyone who has it — no Microsoft login required. Password authentication is still enforced and required. This mode is useful for sharing temporary access, but the terminal is effectively internet-accessible and protected only by the password and rate limiting (5 attempts/min/IP).
43+
44+
### LAN Exposure
45+
46+
With `--lan` or `--host 0.0.0.0`, TermBeam binds to all network interfaces. Any device on your local network can reach the server. On trusted home networks this may be acceptable; on shared or public networks (coffee shops, coworking spaces, hotel Wi-Fi) this is risky.
47+
48+
## Safe Defaults
49+
50+
Out of the box, TermBeam is configured conservatively:
51+
52+
-**Password auto-generated** — a strong random password is created on every run
53+
-**Localhost bind** — server listens on `127.0.0.1` only, not reachable from the network
54+
-**Private tunnel** — tunnel requires Microsoft account login (owner-only access)
55+
-**Ephemeral tunnel** — tunnel URL is deleted when TermBeam exits
56+
-**Security headers** — X-Frame-Options, CSP, no-cache, nosniff on all responses
57+
-**Rate-limited login** — 5 attempts per minute per IP
58+
-**httpOnly cookies** — tokens not accessible to JavaScript
59+
-**WebSocket origin validation** — cross-origin connections rejected
60+
-**Shell path validation** — only detected shells allowed, arbitrary paths rejected
61+
62+
You do not need to change any defaults to use TermBeam safely.
63+
64+
## Dangerous Modes
65+
66+
The following flags increase your attack surface. Use them only when you understand the trade-offs.
67+
68+
### `--public` — Public tunnel access
69+
70+
Removes the Microsoft login requirement from the tunnel. Anyone with the URL can attempt to log in. Mitigated by password auth and rate limiting, but the terminal is internet-facing.
71+
72+
**When acceptable:** Sharing temporary access with someone who doesn't have a Microsoft account.
73+
**Mitigation:** Use a strong password. Close TermBeam when done.
74+
75+
### `--no-password` — Disable authentication
76+
77+
Removes password protection entirely. Any client that can reach the server has full terminal access.
78+
79+
**When acceptable:** Localhost-only use on a single-user machine where no untrusted local processes are running.
80+
**Never combine with:** `--public` (CLI rejects this) or `--lan` on shared networks.
81+
82+
### `--lan` / `--host 0.0.0.0` — Bind to all interfaces
83+
84+
Makes the server reachable from any device on your local network. Combined with `--no-password`, this gives any network device unrestricted terminal access.
85+
86+
**When acceptable:** Trusted home network with password enabled.
87+
**Not recommended:** Public Wi-Fi, shared office networks, or any network with untrusted devices.
88+
89+
### Combining dangerous flags
90+
91+
| Combination | Result | Risk |
92+
| ------------------------ | -------------------------------------- | --------- |
93+
| `--public` | Internet-accessible, password required | ⚠️ Medium |
94+
| `--no-password` | No auth, localhost only | ⚠️ Medium |
95+
| `--lan` | LAN-accessible, password required | ⚠️ Medium |
96+
| `--lan --no-password` | LAN-accessible, no auth | 🔴 High |
97+
| `--public --no-password` | **Blocked by CLI** ||
98+
99+
## Quick Safety Checklist
100+
101+
Before running TermBeam, verify:
102+
103+
- [ ] **Password is enabled** — don't use `--no-password` unless localhost-only on a trusted machine
104+
- [ ] **Tunnel is private** — don't use `--public` unless you specifically need anonymous tunnel access
105+
- [ ] **Bind is localhost** — don't use `--lan` or `--host 0.0.0.0` unless you need LAN access
106+
- [ ] **Close when done** — TermBeam is not a daemon; don't leave it running unattended
107+
- [ ] **Check the network** — on shared/public Wi-Fi, stick to defaults (private tunnel + localhost)
108+
- [ ] **Review the password** — if using `--password`, ensure it's strong (12+ chars, mixed case/symbols)
109+
110+
## Work vs Personal Machine
111+
112+
### Personal machine (home network)
113+
114+
Default settings are appropriate. If you need LAN access (e.g., phone on the same Wi-Fi), `--lan` with the auto-generated password is reasonable.
115+
116+
### Work machine (corporate network)
117+
118+
- **Use defaults** (private tunnel + auto-password) — this routes through your Microsoft account
119+
- **Avoid `--public`** — a public tunnel on a corporate device exposes the terminal to the internet
120+
- **Avoid `--lan`** on corporate networks — other devices on the network could discover the server
121+
- **Don't use for production** — TermBeam is a development tool, not a production remote access solution
122+
123+
### Not recommended
124+
125+
- Running TermBeam with `--public` on machines with access to customer data or secrets
126+
- Using `--no-password --lan` on any network you don't fully control
127+
- Leaving TermBeam running unattended for extended periods
128+
- Using TermBeam as a replacement for SSH, VPN, or proper remote access infrastructure
129+
12130
## Reporting a Vulnerability
13131

14132
**Please do NOT open a public GitHub issue for security vulnerabilities.**
@@ -44,7 +162,7 @@ Instead, report vulnerabilities privately:
44162
When running TermBeam:
45163

46164
- Always use a **strong password** (`--generate-password` or `--password`)
47-
- Bind TermBeam to **localhost** (`--host 127.0.0.1`) unless you need LAN access (default is `0.0.0.0`)
165+
- Bind TermBeam to **localhost** (`--host 127.0.0.1`) unless you need LAN access (default is `127.0.0.1`)
48166
- Use a **reverse proxy with TLS** (nginx, Caddy) for production deployments
49167
- Keep dependencies **up to date** (`npm update`)
50168
- Review the [Security documentation](https://dorlugasigal.github.io/TermBeam/security/) for detailed guidance

docs/security.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,83 @@ TermBeam provides access to a real shell on your machine. **Security is critical
1111

1212
## Threat Model
1313

14-
TermBeam is designed for **trusted local networks**. It is NOT designed as a production-grade remote access tool. Use with caution when exposing to the internet.
14+
TermBeam exposes a real shell over the network. The risk depends entirely on **how** you run it. Understanding the operating modes below helps you make informed decisions.
15+
16+
### Operating Modes
17+
18+
TermBeam has three access layers that combine to determine your risk profile:
19+
20+
| Mode | Command | Who Can Connect | Auth | Risk |
21+
| ----------------------------- | ------------------------------------------ | ------------------------------------- | --------------------------------- | --------- |
22+
| **Default (private tunnel)** | `termbeam` | Only you (Microsoft login + password) | Auto-password + tunnel owner auth | ✅ Low |
23+
| **Public tunnel** | `termbeam --public` | Anyone with the URL + password | Auto-password | ⚠️ Medium |
24+
| **LAN-only (localhost)** | `termbeam --no-tunnel` | Local machine only | Auto-password | ✅ Low |
25+
| **LAN-only (all interfaces)** | `termbeam --no-tunnel --lan` | Any device on your network | Auto-password | ⚠️ Medium |
26+
| **Localhost, no password** | `termbeam --no-tunnel --no-password` | Local processes only | None | ⚠️ Medium |
27+
| **LAN, no password** | `termbeam --no-tunnel --no-password --lan` | Anyone on your network | None | 🔴 High |
28+
29+
!!! warning "`--public --no-password` is blocked"
30+
The CLI refuses to start with a public tunnel and no password.
31+
32+
### Private Tunnel (Default)
33+
34+
The default mode creates an ephemeral [Azure DevTunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/) that requires **two layers of authentication**:
35+
36+
1. **Microsoft account login** — only the tunnel owner can access the URL
37+
2. **TermBeam password** — auto-generated on each run
38+
39+
This is the safest way to access your terminal remotely. The tunnel URL is HTTPS, the connection is encrypted end-to-end, and the URL is unguessable.
40+
41+
### Public Tunnel
42+
43+
With `--public`, the tunnel URL is accessible to anyone who has it — no Microsoft login required. Password authentication is still enforced. This mode is useful for sharing temporary access, but the terminal is internet-accessible and protected only by the password and rate limiting (5 attempts/min/IP).
44+
45+
### LAN Exposure
46+
47+
With `--lan` or `--host 0.0.0.0`, TermBeam binds to all network interfaces. Any device on your local network can reach the server. On trusted home networks this may be acceptable; on shared or public networks (coffee shops, coworking spaces, hotel Wi-Fi) this is risky.
48+
49+
### Safe Defaults
50+
51+
Out of the box, TermBeam is configured conservatively:
52+
53+
-**Password auto-generated** — a strong random password is created on every run
54+
-**Localhost bind** — server listens on `127.0.0.1` only
55+
-**Private tunnel** — tunnel requires Microsoft account login (owner-only)
56+
-**Ephemeral tunnel** — tunnel URL is deleted when TermBeam exits
57+
-**Security headers** — X-Frame-Options, CSP, no-cache, nosniff on all responses
58+
-**Rate-limited login** — 5 attempts per minute per IP
59+
-**httpOnly cookies** — tokens not accessible to JavaScript
60+
-**WebSocket origin validation** — cross-origin connections rejected
61+
-**Shell path validation** — only detected shells allowed
62+
63+
### Dangerous Modes
64+
65+
The following flags increase your attack surface. Use them only when you understand the trade-offs.
66+
67+
| Flag | Effect | When Acceptable |
68+
| -------------------------- | ----------------------------------- | ----------------------------------------------------------------- |
69+
| `--public` | Removes Microsoft login from tunnel | Sharing temporary access with someone without a Microsoft account |
70+
| `--no-password` | Removes password auth | Localhost-only on a single-user machine |
71+
| `--lan` / `--host 0.0.0.0` | Binds to all interfaces | Trusted home network with password enabled |
72+
| `--lan --no-password` | LAN-accessible, no auth | **Not recommended** |
73+
74+
### Quick Safety Checklist
75+
76+
Before running TermBeam, verify:
77+
78+
- [ ] **Password is enabled** — don't use `--no-password` unless localhost-only on a trusted machine
79+
- [ ] **Tunnel is private** — don't use `--public` unless you specifically need anonymous tunnel access
80+
- [ ] **Bind is localhost** — don't use `--lan` unless you need LAN access on a trusted network
81+
- [ ] **Close when done** — TermBeam is not a daemon; don't leave it running unattended
82+
- [ ] **Check the network** — on shared/public Wi-Fi, stick to defaults
83+
84+
### Work vs Personal Machine
85+
86+
**Personal machine (home network):** Default settings are appropriate. If you need LAN access (e.g., phone on the same Wi-Fi), `--lan` with the auto-generated password is reasonable.
87+
88+
**Work machine (corporate network):** Use defaults (private tunnel + auto-password). Avoid `--public` and `--lan` on corporate networks. TermBeam is a development tool, not a production remote access solution.
89+
90+
!!! danger "Not Recommended" - Running TermBeam with `--public` on machines with access to customer data or secrets - Using `--no-password --lan` on any network you don't fully control - Leaving TermBeam running unattended for extended periods - Using TermBeam as a replacement for SSH, VPN, or proper remote access infrastructure
1591

1692
## Security Features
1793

test/routes.test.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('Routes', () => {
8282

8383
it('GET /uploads/:id should serve uploaded file', async () => {
8484
if (!inst) inst = await startServer();
85-
const imageData = Buffer.from('fakepngdata');
85+
const imageData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00]);
8686
const uploadRes = await httpRequest(
8787
{
8888
hostname: '127.0.0.1',
@@ -187,10 +187,22 @@ describe('Routes', () => {
187187
if (!inst) inst = await startServer();
188188
// RIFF....WEBP header
189189
const imageData = Buffer.from([
190-
0x52, 0x49, 0x46, 0x46, // RIFF
191-
0x24, 0x00, 0x00, 0x00, // file size
192-
0x57, 0x45, 0x42, 0x50, // WEBP
193-
0x00, 0x00, 0x00, 0x00,
190+
0x52,
191+
0x49,
192+
0x46,
193+
0x46, // RIFF
194+
0x24,
195+
0x00,
196+
0x00,
197+
0x00, // file size
198+
0x57,
199+
0x45,
200+
0x42,
201+
0x50, // WEBP
202+
0x00,
203+
0x00,
204+
0x00,
205+
0x00,
194206
]);
195207
const res = await httpRequest(
196208
{
@@ -226,9 +238,18 @@ describe('Routes', () => {
226238
if (!inst) inst = await startServer();
227239
// Has WEBP at offset 8 but no RIFF at offset 0
228240
const imageData = Buffer.from([
229-
0x00, 0x00, 0x00, 0x00, // NOT RIFF
230-
0x24, 0x00, 0x00, 0x00,
231-
0x57, 0x45, 0x42, 0x50, // WEBP
241+
0x00,
242+
0x00,
243+
0x00,
244+
0x00, // NOT RIFF
245+
0x24,
246+
0x00,
247+
0x00,
248+
0x00,
249+
0x57,
250+
0x45,
251+
0x42,
252+
0x50, // WEBP
232253
]);
233254
const res = await httpRequest(
234255
{

0 commit comments

Comments
 (0)