Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c12be68
feat(onboarding): discovery pills, hover-hold gestures, hover-intent …
tmlxrd May 26, 2026
a0d3853
chore: bump version to 0.1.5
tmlxrd May 26, 2026
9098e54
feat(favicon): dynamic rounded favicon with aggregate status dots
tmlxrd May 27, 2026
759a8bb
fix(gateway): honest connection status and usable screen when agents …
tmlxrd May 27, 2026
ec72068
remote-access: outbound tunnel client (v0)
tmlxrd May 28, 2026
c37d13f
remote-access: passphrase gate for tunneled requests
tmlxrd May 28, 2026
c4dc59b
remote-access: forward WebSocket upgrades through the tunnel
tmlxrd May 28, 2026
9382d0b
remote-access: Settings page + duration-bounded tunnels + persistence
tmlxrd May 28, 2026
27e798a
remote-access: multi-tunnel — independent URLs, deletions don't cross…
tmlxrd May 28, 2026
b75b161
settings: minimal Apple-style /settings page; Remote Access is a section
tmlxrd May 28, 2026
b17ac52
settings: native iClaw styling; smaller gear
tmlxrd May 28, 2026
1b166bd
settings: polish Remote Access UI
tmlxrd May 28, 2026
3137ecc
settings: modal-driven create, label support, sort by expiry, calmer …
tmlxrd May 28, 2026
30221fd
remote-access: single multiplexed WS to relay, multi-tunnel inside
tmlxrd May 28, 2026
cbe4d65
remote-access: default relay → wss://relay.iclaw.digital/tunnel
tmlxrd May 28, 2026
2f9a438
settings: prettier modal + branded login page
tmlxrd May 28, 2026
72a6967
settings: simplify Remote Access UI and copy
tmlxrd May 28, 2026
2b99242
settings: refine Share modal and Remote Access UX
tmlxrd May 28, 2026
8b54e45
remote-access: restyle tunneled passphrase gate page
tmlxrd May 28, 2026
5fa3dcd
settings: adaptive Remote Access tunnel polling
tmlxrd May 28, 2026
fa57427
Merge pull request #14 from iClawApp/feat/remote-access
tmlxrd May 28, 2026
cac3e36
test: add openclaw.health to the projects-route mock
tmlxrd May 28, 2026
60bb948
remote-access: WS keep-alive and stable URL on short disconnects
tmlxrd May 28, 2026
1dcda91
ui: mobile layout for phone-sized viewports (≤639px)
tmlxrd May 28, 2026
c0d2aaa
remote-access: E2E alpha only — OPAQUE, encrypted transport, device s…
tmlxrd May 29, 2026
5f190f7
remote-access: E2E alpha (OPAQUE + encrypted transport) + post-audit …
tmlxrd May 29, 2026
a49b189
add relay capture hook for e2e smoke
tmlxrd May 29, 2026
b044d03
fix: settle turns on lifecycle:end, not intermediate chat:final
tmlxrd May 29, 2026
4339cb8
new version
tmlxrd May 29, 2026
7e48574
fix: sort scheduled messages in composer by soonest time
tmlxrd May 29, 2026
e14ea25
remote-access: fix E2E navigation dead-ends, dedup devices, harden pl…
tmlxrd May 29, 2026
751c0e4
feat: show fuchsia sidebar dot for chats with scheduled messages
tmlxrd May 29, 2026
633b2be
style: use muted stone gray for scheduled chat sidebar dot
tmlxrd May 29, 2026
9d8fdd6
fix: load iclaw.js on settings so sidebar search works
tmlxrd May 29, 2026
bab1578
style: remove external-link icon from remote access URL row
tmlxrd May 29, 2026
9dd375d
remote-access: auto-generate & persist OPAQUE server setup (no manual…
tmlxrd May 29, 2026
57e1d6a
remote-access: serve public app-shell assets in the clear (fix blank …
tmlxrd May 29, 2026
b0ca064
remote-access: fix E2E WebSocket bypass (messages now send)
tmlxrd May 29, 2026
aa55345
remote-access: gated client diagnostics for the gate/resume flow
tmlxrd May 29, 2026
10bec23
remote-access: serve root-level public static files (favicons/PWA ico…
tmlxrd May 29, 2026
07eed13
remote-access: gated WS/send diagnostics in iclaw.js (debug 'can't se…
tmlxrd May 29, 2026
3ee0459
remote-access: cache-bust iclaw.js + WS recv diagnostics
tmlxrd May 30, 2026
264116c
remote-access: fix E2E message send (WebSocket.OPEN + UTF-8) + WS kee…
tmlxrd May 30, 2026
85065f6
test(remote-access): make relay token-parity check optional in CI
tmlxrd May 30, 2026
635ff5b
feat(remote-access): headless onboarding + consistent startup UI
tmlxrd May 31, 2026
537e583
Merge pull request #15 from iClawApp/feat/headless-remote-access
tmlxrd May 31, 2026
4c18924
remote-access: add tunnel ownership proof + widen tunnelId (relay H1)
tmlxrd May 31, 2026
bc3b1a6
remote-access: store device key as non-extractable CryptoKey (L8)
tmlxrd May 31, 2026
5798407
tasks: make OpenClaw respond in the user's language
tmlxrd May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ PORT=3000
# server-generated metadata stays consistent (the share modal link is built
# from this value in the browser).
# ICLAW_CLOUD_URL=http://localhost:4000

# ── Remote Access (E2E alpha) ─────────────────────────────────────────────────
# WebSocket URL of iclaw-relay. Unset = Remote Access disabled in Settings UI.
# ICLAW_RELAY_URL=wss://relay.example.com/tunnel
# OPAQUE_SERVER_SETUP= # required when ICLAW_RELAY_URL is set
# # npx @serenity-kit/opaque create-server-setup
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ data/
.env.local
.DS_Store
*.log
.claude
*.tgz
.claude
public/js/vendor/opaque/
public/js/vendor/noble/
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

All notable changes to iClaw are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/) and the project uses [Semantic Versioning](https://semver.org/) starting at v0.1.0.

## [Unreleased]

### Added

- **Send-button discovery pill.** Above the Send button, "Did you know? Hold Send for more" surfaces the hidden long-press menu (Scheduled message / Create task). Server gates on an "ever-created" threshold — ≥ 2 tasks AND ≥ 3 scheduled messages, read from `sqlite_sequence` so the metric survives row deletion. Browser throttles to once per day. Auto-dismiss on textarea focus, Send pointerdown, or 12 s timer.
- **Sidebar discovery pill.** "Tip: right-click a chat for options" under the toolbar. Hides forever after the first contextmenu (or hover-hold) on a chat item; otherwise throttled to once per day.
- **Hover-hold gestures (1.5 s)** — open the same menus as long-press / right-click without clicking. Cursor parked on the Send button opens the schedule menu; cursor parked on a sidebar chat item opens the context menu.
- **Hover-intent auto-close (3.5 s)** for both menus. Mouse leaves the menu → 3.5 s timer; returns → timer resets. Replaces the schedule menu's old 10 s blanket timeout.
- **Dynamic favicon.** The browser tab icon is now canvas-rendered with Apple-style rounded corners, and carries up to two status dots aggregated across all chats/tasks: 🟠 needs-human, 🔵 chat ready / task-review, 🟢 working (priority orange → blue → green). It's a derived view of the sidebar status dots — repainted only when the verdict actually changes (debounced), never animated, so steady-state cost is zero.

### Removed

- Native browser tooltip on sidebar chat items (`title="<chat.title>"`). The full title is already visible inline; the tooltip just got in the way of the new hover-hold gesture.

## [0.1.4] — 2026-05-26

### Removed
Expand Down
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ Hit **Share** in any chat to get an encrypted link. The chat is encrypted in you

Powered by [iClaw-cloud](https://github.com/iClawApp/iClaw-cloud) — defaults to `https://app.iclaw.digital`.

## Remote Access (alpha)

Open the iClaw UI from another device through an **iclaw-relay** tunnel (Settings → Remote Access).

**Security model (summary):**

- **Relay access token** — blocks visitors who only guess the subdomain.
- **OPAQUE login** — passphrase is not sent in plaintext over the tunnel.
- **Encrypted HTTP/WebSocket** (E2E alpha) — payloads are encrypted between the browser and **local** iClaw; the relay forwards encrypted frames only.
- **Device sessions** — trusted browsers can reconnect without retyping the passphrase.
- **Alpha** — not externally audited. The relay still sees metadata (subdomain, timing, sizes, E2E endpoint paths).

Two setups: **local mode** (iClaw + OpenClaw on the same laptop) and **host mode** (always-on machine, browse from phone/laptop). See [docs/REMOTE_ACCESS.md](../docs/REMOTE_ACCESS.md).

Env on the iClaw host: `ICLAW_RELAY_URL` and `OPAQUE_SERVER_SETUP` (required when Remote Access is enabled).

---

## For developers
Expand All @@ -51,7 +67,13 @@ cd iClaw && npm install && npm run dev
| Gateway | http://127.0.0.1:18789 (`OPENCLAW_BASE_URL`) |

Optional env vars: [.env.example](.env.example).
Architecture + coding rules: [AGENTS.md](AGENTS.md).
Architecture + coding rules: [AGENTS.md](AGENTS.md).
Remote Access alpha: [docs/REMOTE_ACCESS.md](../docs/REMOTE_ACCESS.md), smoke [docs/REMOTE_ACCESS_SMOKE.md](../docs/REMOTE_ACCESS_SMOKE.md).

```bash
npm run test:ra-smoke # E2E adversarial smoke (vitest)
npm run scan:relay-capture -- frames.ndjson # scan relay frame capture
```

## Star history

Expand Down
176 changes: 176 additions & 0 deletions docs/REMOTE_ACCESS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Remote Access (E2E alpha)

Remote Access gives you a temporary public URL that lets you open this iClaw
from another device — your phone, another laptop, or someone you trust —
without opening any inbound port on your machine.

> **Alpha. Not externally audited.** The encryption design below is real and
> tested, but it has not had an independent security review. Treat it as
> "much better than plaintext," not as a hardened, audited product. See
> [Security model](#security-model) for exactly what is and isn't protected.

---

## How it works (one paragraph)

Your local iClaw opens **one outbound WebSocket** to a public **relay**
(`relay.iclaw.digital`). The relay hands out a temporary subdomain
(`https://<name>.iclaw.digital`) and forwards traffic back down that
WebSocket. A visitor opens the URL, passes the relay **access gate** (a
one-time `?access=` token baked into the link), then logs in with the
**passphrase** via **OPAQUE** (the passphrase never crosses the wire). After
login, every HTTP request and WebSocket message is **encrypted end-to-end**
between the visitor's browser and your local iClaw; the relay only ever sees
ciphertext envelopes.

```
[Browser] --TLS--> [Cloudflare] --> [relay] --(single WS)--> [your iClaw]
\__________ E2E (OPAQUE-derived keys) __________/
relay sees only ciphertext + metadata
```

---

## Setup

### 1. OPAQUE server setup — automatic

Nothing to do. The first time you create a tunnel, iClaw generates an OPAQUE
server setup and stores it locally (in its SQLite, alongside the passphrases
and access tokens it already keeps). Fresh installs "just work".

**Optional override** — set `OPAQUE_SERVER_SETUP` to pin a specific value
(advanced / shared-host scenarios). The env var always wins over the
auto-generated one:

```bash
# generate one explicitly, if you want to control it:
npx @serenity-kit/opaque create-server-setup # prints a base64 string
OPAQUE_SERVER_SETUP=<the base64 string from above>
```

- Keep it **secret** (don't commit it; `.env` should be git-ignored) and
**stable**. If it changes, existing tunnels' stored OPAQUE records are
invalidated and re-registered on next start.
- To run the **same** tunnel/passphrase across several of your machines, set
the **same** `OPAQUE_SERVER_SETUP` on each; otherwise each host gets its own
auto-generated value (independent).

### 2. (Optional) point at a different relay

By default iClaw uses the hosted relay:

```
wss://relay.iclaw.digital/tunnel
```

Override for local development against your own `iclaw-relay`:

```bash
ICLAW_RELAY_URL=ws://127.0.0.1:4100/tunnel
```

### 3. Create a tunnel

Open iClaw → gear icon → **Settings → Remote Access → Share**. Pick a name
and a duration (30 min / 12 h / 7 d / 30 d). You get:

- a **URL** (`https://<name>.iclaw.digital?access=…`) — already contains the
one-time relay access token,
- a **passphrase** (4 words + digits) — shown separately.

Share **both**, ideally over a private channel. The visitor opens the URL and
enters the passphrase.

---

## Local mode vs always-on host

- **Local mode (default):** iClaw runs on your own machine and the tunnel
lives only while iClaw is running and the tunnel hasn't expired. Quitting
iClaw drops the tunnel; restarting within ~10 min restores the **same** URL
(the relay reserves the subdomain during a short grace window). After that
the URL changes on next start.
- **Always-on host:** run iClaw on a server you control (same `OPAQUE_SERVER_SETUP`
in its env) so the tunnel survives across your laptop sleeping. Same code,
just a longer-lived host. The relay is still in the trust boundary for
metadata (see below).

---

## Managing access

- **New access link** (per tunnel) rotates the relay access token: old links
and any already-issued access cookies stop working immediately; a fresh
`?access=` link is minted. The passphrase and URL host stay the same.
- **Devices:** after a successful passphrase login, a browser registers a
device keypair (private key stays in the browser) and appears under
*Connected devices*; revoke individual devices from the tunnel card.
> **Alpha limitation:** the E2E session keys live only in the tab's
> `sessionStorage`, so reopening the link in a new tab (or after closing it)
> currently **requires re-entering the passphrase** to re-derive them — the
> device record updates "last seen" but does not yet skip the passphrase for
> the encrypted session. Device-based E2E resume is planned, not shipped.
- **Disable** tears the tunnel down immediately; the URL 404s.

---

## Security model

**What is protected**

- **Passphrase** — never sent over the wire. OPAQUE proves knowledge of it
without transmitting it; the relay cannot learn it.
- **HTTP & WebSocket payloads that carry your data** — encrypted end-to-end
(AES-256-GCM with per-stream subkeys derived from the OPAQUE session key via
HKDF). Page contents (the rendered HTML), `/api` calls, chat messages,
uploads/media, and the live WebSocket all travel as ciphertext envelopes; the
relay cannot read their paths, bodies, responses, or content.
- **Tamper / replay** — each record is authenticated (GCM tag over a context
AAD: tunnelId, streamId, direction, counter, frame kind, relay binding) and
a per-stream monotonic counter ledger rejects replays.

**What the relay still sees (metadata)**

- The subdomain in use, connection timing, and approximate request/response
**sizes**.
- Which **E2E endpoints** are hit (`/__ra/e2e/http`, `/__ra/e2e/ws`) and the
bootstrap/OPAQUE/gate-asset requests that happen before the encrypted
session is established.
- The **public app-shell assets** — stylesheets, scripts and icons under
`/css/*`, `/js/*` (incl. vendored libs) and the favicons. These are loaded by
the browser via `<link>`/`<script>` tags (which can't be E2E-wrapped) and are
byte-identical for every visitor, so serving them in the clear leaks nothing
about you. **Your data is not among them** — pages, `/api`, `/uploads`,
`/media` and the WebSocket are E2E.
- The relay **access cookie** (`iclaw_tunnel_access`) that rides the outer
requests. It gates the subdomain but does **not** grant the relay access to
encrypted content (that requires the OPAQUE-derived keys, which never leave
the browser/iClaw).
- The iClaw login session (`iclaw_ra`) is **not** exposed to the relay: it is
never handed to the browser, so it never rides an outer request. Inner
encrypted requests are authenticated by possession of the E2E keys, and your
local iClaw re-attaches the session id itself at loopback.

**What this is NOT**

- Not an audited, production-grade E2E system.
- Not anonymous — the relay operator can see you have an active tunnel and
its traffic metadata.
- The browser code that performs the crypto is served **through the relay**;
a malicious relay could in principle serve modified JS. Browser-delivered
E2E is only as trustworthy as code delivery. A native client would close
this gap; it is out of scope for the alpha.

---

## Troubleshooting

| Symptom | Cause / fix |
| --- | --- |
| "Share" returns 503 `opaque_setup_unavailable` | OPAQUE setup is auto-generated now; this only happens if the OPAQUE runtime can't load. Check logs / reinstall deps. On older builds it was `opaque_setup_missing` — set `OPAQUE_SERVER_SETUP` or update iClaw. |
| Login says "Remote Access login is not ready" | OPAQUE registration not synced — restart iClaw. |
| URL changed after a restart | Expected if iClaw was down longer than the ~10 min reconnect grace, or the relay restarted. |
| Visitor sees "tunnel reconnecting" | iClaw briefly lost its relay WS; it retries automatically. |
| `403 Forbidden` on the URL | Missing/expired `?access=` token — use the full link, or mint a new one with **New access link**. |
| Raw `{"error":"E2E transport required …"}` instead of the page | A returning tab hit the workspace over plaintext. Fixed: plaintext navigations now always serve the gate. If you still see it, your iClaw predates the fix — rebuild/restart it. Re-enter the passphrase to start a fresh encrypted session. |
47 changes: 38 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@iclawapp/iclaw",
"version": "0.1.4",
"version": "0.2.0",
"description": "Local web UI for OpenClaw Gateway",
"license": "MIT",
"homepage": "https://github.com/iClawApp/iClaw",
Expand Down Expand Up @@ -31,9 +31,17 @@
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"prepack": "npm run build"
"test:ra-smoke": "vitest run test/unit/remoteAccessE2eSmoke.test.ts",
"test:ra-integration": "npm run build && node scripts/ra-e2e-integration.mjs",
"smoke:ra-manual": "node scripts/ra-e2e-smoke.mjs",
"scan:relay-capture": "node scripts/ra-relay-capture-scan.mjs",
"postinstall": "node scripts/copy-opaque-vendor.mjs",
"prepack": "node scripts/copy-opaque-vendor.mjs && node scripts/verify-vendor-assets.mjs && npm run build",
"verify:vendor": "node scripts/verify-vendor-assets.mjs"
},
"dependencies": {
"@noble/hashes": "^2.2.0",
"@serenity-kit/opaque": "^1.1.0",
"better-sqlite3": "^11.3.0",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
Expand Down
Loading
Loading