hop2 0.11.47 → 0.11.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README-MCP.md +3 -2
- package/README.md +12 -6
- package/SECURITY.md +102 -0
- package/hop +488 -86
- package/lib/audit-rotation.js +113 -0
- package/lib/auth-sessions.js +194 -0
- package/lib/registration.js +9 -6
- package/mcp/hop-mcp.js +49 -5
- package/package.json +1 -1
- package/tests/audit-rotation.unit.test.cjs +74 -0
- package/tests/auth-sessions.integration.test.cjs +315 -0
- package/tests/auth-sessions.unit.test.cjs +96 -0
- package/tests/hop-mcp.integration.test.cjs +4 -4
- package/tests/registration.integration.test.cjs +3 -1
- package/tests/registration.unit.test.cjs +14 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Jian Zhou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README-MCP.md
CHANGED
|
@@ -221,8 +221,9 @@ Helper tools (`hopx_`): convenience wrappers built on top of core tools.
|
|
|
221
221
|
- `hopx_agents_overview` — fleet status in one call: every agent-created/agent-permitted session with state (running/busy/idle), cwd, foreground program, Claude turn count, bell counters, last activity, and pending wait jobs; `include_user_sessions=false` narrows to agent-created only, `include_ports=true` adds proxy sessions
|
|
222
222
|
- `hopx_wait_any` — race several background waits (wait_ids and/or terminal_ids, auto-starting `until_agent_done` waits); returns the first real completion(s) plus the still-pending set; expired watches are re-armed automatically (new wait_id in `pending`) rather than reported as completions
|
|
223
223
|
- `hopx_spawn_agent` — one-call subagent bring-up: create terminal + launch agent CLI (claude/codex/gemini/custom) + wait until ready + optionally run a first task with an automatic completion contract; `isolation="worktree"` gives the worker its own git worktree + fleet/ branch for overlapping-file fleets
|
|
224
|
-
- the Claude preset uses the `
|
|
225
|
-
- the
|
|
224
|
+
- the Claude preset uses the `opus` model alias and `bypassPermissions` mode by default, and removes inherited Claude Code process credentials before launch, so a delegated worker uses the host's durable `claude auth login` without parking on approval prompts; pass `permission_mode="manual"` for a restricted worker, `args="--model ..."` to choose another model, or an explicit `command` when inherited credentials are intentional
|
|
225
|
+
- the host operator can cap the mode with `hop config agent-ceiling <mode>` (or `HOP_AGENT_PERMISSION_CEILING`); a request above the cap is clamped, the applied mode is returned as `permission_mode`, and the clamp is explained in `permission_note`
|
|
226
|
+
- the Codex preset runs autonomously and bypasses workspace-trust, approval, and self-update startup gates when the ceiling is `bypassPermissions` (the default); under any lower ceiling it runs `--full-auto` (sandboxed, asks on request). Hop rejects detected login, trust, or update screens instead of dispatching a task into them
|
|
226
227
|
- MCP and `hopa spawn --task` wait by default and return the transcript-backed `assistant_reply`; `async=true` is for fleet dispatch through a long-lived MCP connection, while the short-lived `hopa` CLI rejects async waits
|
|
227
228
|
- `hopx_task_ledger` — the durable orchestration ledger (~/.hop2/orchestration): async dispatches with contracts survive manager/MCP restarts; pending entries reconcile lazily from turn counters + transcripts; acknowledge to consume
|
|
228
229
|
|
package/README.md
CHANGED
|
@@ -290,6 +290,8 @@ Paths:
|
|
|
290
290
|
- workspace-backed sessions: `~/.hop2/workspaces/<workspace>/history/<internal-session>.history`
|
|
291
291
|
- workspace-backed audit logs: `~/.hop2/workspaces/<workspace>/logs/<internal-session>/audit.ndjson`
|
|
292
292
|
|
|
293
|
+
Retention: a session's live `audit.ndjson` rotates at 64 MB (`HOP_SESSION_AUDIT_MAX_MB`, keep `HOP_SESSION_AUDIT_KEEP`=5 rotations), and a *dead* session's whole log directory is deleted 90 days after its last write (`HOP_SESSION_AUDIT_RETENTION_DAYS`; `0` disables). Live sessions are never swept.
|
|
294
|
+
|
|
293
295
|
Key tunables:
|
|
294
296
|
|
|
295
297
|
- `HOP_SESSION_HISTORY_SIZE`
|
|
@@ -298,6 +300,8 @@ Key tunables:
|
|
|
298
300
|
- `HOP_AGENT_POST_START_HISTORY_INIT`
|
|
299
301
|
- `HOP_POST_START_HISTORY_INIT`
|
|
300
302
|
- `HOP_SESSION_AUDIT_INLINE_MAX_BYTES`
|
|
303
|
+
- `HOP_SESSION_AUDIT_MAX_MB`, `HOP_SESSION_AUDIT_KEEP`, `HOP_SESSION_AUDIT_RETENTION_DAYS`
|
|
304
|
+
- `HOP_SESSION_TTL_DAYS` (login session lifetime, default 30)
|
|
301
305
|
- `HOP_SESSION_TUI_KEYFRAME_INTERVAL_MS`
|
|
302
306
|
- `HOP_SESSION_TUI_KEYFRAME_TAIL_CHARS`
|
|
303
307
|
- `HOP_SESSION_TUI_KEYFRAME_MAX_LINES`
|
|
@@ -360,13 +364,15 @@ Notes:
|
|
|
360
364
|
|
|
361
365
|
### Security
|
|
362
366
|
|
|
363
|
-
-
|
|
364
|
-
-
|
|
365
|
-
-
|
|
366
|
-
- The
|
|
367
|
-
- Cloudflare Tunnel provides end-to-end TLS
|
|
367
|
+
- Login is password + TOTP, or a passkey (Touch ID / Face ID / security key). **A password is required before the public tunnel opens**; `hop config totp-only on` opts out.
|
|
368
|
+
- Every login gets its **own per-device session** (random token, stored hashed), valid for 30 days (`HOP_SESSION_TTL_DAYS`). `hop auth` lists logged-in devices, `hop auth revoke <id>` logs one out, `hop auth revoke --all` logs everyone out, `hop auth token` mints a revocable token for an app or script.
|
|
369
|
+
- Login cookies are `httpOnly`, `secure`, `sameSite=lax`, and host-scoped; they survive `hop stop/start`.
|
|
370
|
+
- The daemon's own secret is only for local processes (CLI, MCP, hay) as a Bearer header; it is never a cookie.
|
|
371
|
+
- The local server binds only to `127.0.0.1`; Cloudflare Tunnel provides end-to-end TLS.
|
|
372
|
+
- Audit logs rotate at 64 MB and dead sessions' logs are removed after 90 days (see Logging).
|
|
373
|
+
- `hop config agent-ceiling <mode>` caps the permission mode spawned agents may run with.
|
|
368
374
|
|
|
369
|
-
Secrets are stored in `~/.hop2/`.
|
|
375
|
+
Secrets are stored in `~/.hop2/`. Full threat model, what an authenticated user can do, and how to report a hole: [SECURITY.md](SECURITY.md).
|
|
370
376
|
|
|
371
377
|
### Runtime and Recovery
|
|
372
378
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Hop puts a shell on the public internet on purpose. This page says exactly what
|
|
4
|
+
stands between the internet and that shell, what an attacker who gets past it
|
|
5
|
+
can do, and how to report a hole.
|
|
6
|
+
|
|
7
|
+
## Reporting
|
|
8
|
+
|
|
9
|
+
Email **jianzhou@uchicago.edu** with "hop security" in the subject. Please do
|
|
10
|
+
not open a public issue for anything exploitable. You will get an
|
|
11
|
+
acknowledgement within a few days and a fix or a plan within two weeks for
|
|
12
|
+
anything confirmed.
|
|
13
|
+
|
|
14
|
+
## Threat model
|
|
15
|
+
|
|
16
|
+
- **In scope:** an attacker on the internet reaching your tunnel URL; a
|
|
17
|
+
stranger who obtained a device's cookie; a lab member on a shared landing
|
|
18
|
+
host trying to reach another member's instance; an agent (yours or a
|
|
19
|
+
prompt-injected one) doing more than you meant through the MCP surface.
|
|
20
|
+
- **Out of scope:** an attacker who already has your local user account, your
|
|
21
|
+
Cloudflare account, or root on the machine. Hop runs as you; it cannot
|
|
22
|
+
protect you from you.
|
|
23
|
+
|
|
24
|
+
## How a request gets in
|
|
25
|
+
|
|
26
|
+
1. **TLS and the tunnel.** The daemon binds `127.0.0.1` only. Cloudflare
|
|
27
|
+
Tunnel terminates TLS at Cloudflare and connects to the daemon over
|
|
28
|
+
loopback. Nothing listens on a public port.
|
|
29
|
+
2. **Login.** One of:
|
|
30
|
+
- password (scrypt, salted) **plus** a TOTP code (`otplib`, 30-second
|
|
31
|
+
steps), or
|
|
32
|
+
- a passkey (WebAuthn; Touch ID, Face ID, security key), enrolled from an
|
|
33
|
+
already-authenticated session and bound to the hostname.
|
|
34
|
+
Failed attempts back off per IP: 5 failures → 30 s, doubling.
|
|
35
|
+
3. **Per-device sessions.** A successful login mints a random 256-bit token
|
|
36
|
+
for *that device* (stored only as a SHA-256 hash in
|
|
37
|
+
`~/.hop2/.auth-sessions.json`, mode 0600), set as an `httpOnly`, `Secure`,
|
|
38
|
+
`SameSite=Lax`, host-scoped cookie valid for **30 days**
|
|
39
|
+
(`HOP_SESSION_TTL_DAYS`). `hop auth` lists devices; `hop auth revoke <id>`
|
|
40
|
+
logs one out; `hop auth revoke --all` logs everyone out. Logging out
|
|
41
|
+
revokes the session server-side.
|
|
42
|
+
4. **Local processes.** The hop CLI, MCP server and hay talk to the daemon
|
|
43
|
+
with a per-install secret (`~/.hop2/.session_secret`, 0600) as an
|
|
44
|
+
`Authorization: Bearer` header. That secret is **never** accepted as a
|
|
45
|
+
cookie, and in a WebSocket `?token=` query only on a direct loopback
|
|
46
|
+
connection (never through the tunnel). `hop auth token` mints a
|
|
47
|
+
revocable device token for apps and scripts instead.
|
|
48
|
+
|
|
49
|
+
## Password is required for remote access
|
|
50
|
+
|
|
51
|
+
TOTP alone is one factor. Hop will not open the public tunnel until a
|
|
52
|
+
password is set (`hop password set`). If you genuinely want
|
|
53
|
+
authenticator-only login — say, behind Cloudflare Access — opt out with
|
|
54
|
+
`hop config totp-only on`; `hop status` shows the policy in force.
|
|
55
|
+
|
|
56
|
+
## What an authenticated user can do
|
|
57
|
+
|
|
58
|
+
Everything you can do in a terminal on that machine, as your user. There is
|
|
59
|
+
no sandbox between the browser and the PTY. Treat the login page as the only
|
|
60
|
+
boundary, and treat MCP access the same way: an MCP client that can reach the
|
|
61
|
+
daemon can run `hopx_exec`.
|
|
62
|
+
|
|
63
|
+
## Agents
|
|
64
|
+
|
|
65
|
+
`hopx_spawn_agent` launches Claude Code with `--permission-mode
|
|
66
|
+
bypassPermissions` by default so delegated work never parks on a prompt.
|
|
67
|
+
Cap that on any host you do not fully trust with
|
|
68
|
+
`hop config agent-ceiling <mode>` (for example `acceptEdits`); requests above
|
|
69
|
+
the ceiling are clamped and the clamp is reported in the tool result. The
|
|
70
|
+
Codex preset drops its approval bypass and runs `--full-auto` (sandboxed)
|
|
71
|
+
under any ceiling below the top. This is a safety rail against agent
|
|
72
|
+
accidents, not a security boundary: MCP access already means shell access.
|
|
73
|
+
|
|
74
|
+
## Audit logs
|
|
75
|
+
|
|
76
|
+
Every session's input and output is recorded to
|
|
77
|
+
`~/.hop2/**/logs/<session>/audit.ndjson`. It is a plaintext record of
|
|
78
|
+
everything typed, including anything pasted into the terminal. The daemon
|
|
79
|
+
rotates each file at 64 MB (`HOP_SESSION_AUDIT_MAX_MB`), keeps 5 rotations
|
|
80
|
+
(`HOP_SESSION_AUDIT_KEEP`), and deletes a *dead* session's log directory 90
|
|
81
|
+
days after its last write (`HOP_SESSION_AUDIT_RETENTION_DAYS`; 0 disables).
|
|
82
|
+
Live sessions are never swept. If you need shorter retention for compliance,
|
|
83
|
+
lower the day count; if you need none, set it to 0 and manage the directory
|
|
84
|
+
yourself.
|
|
85
|
+
|
|
86
|
+
## Multi-user hosts and self-service signup
|
|
87
|
+
|
|
88
|
+
Each lab member runs their own daemon on their own machine. The admin's
|
|
89
|
+
daemon only issues subdomains and tunnels. Cookies are host-scoped (no
|
|
90
|
+
`Domain=`), so one member's session never reaches another's host. Signup is
|
|
91
|
+
off by default, requires an explicit allowed email domain
|
|
92
|
+
(`hop registration domain <d>`), and gates identity through either a mailed
|
|
93
|
+
confirmation link or a verified Cloudflare Access JWT. An approved user can
|
|
94
|
+
serve anything they like at their subdomain; only approve people you would
|
|
95
|
+
give a subdomain of your own domain to.
|
|
96
|
+
|
|
97
|
+
## Files that are secrets
|
|
98
|
+
|
|
99
|
+
Under `~/.hop2/` (all 0600): `.auth_secret` (TOTP seed), `.password_hash`,
|
|
100
|
+
`.session_secret` (local IPC), `.auth-sessions.json` (session hashes),
|
|
101
|
+
`.passkeys.json` (public keys only), tunnel credentials/tokens, and the
|
|
102
|
+
`workspaces/**/logs` audit trail. Back them up like passwords.
|