Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Optional: zync-survey API base URL (no trailing slash).
# Dev default in code is http://127.0.0.1:8090 when unset.
# Production releases MUST bake this via GitHub Actions secret VITE_SURVEY_API_URL.
# VITE_SURVEY_API_URL=https://survey.example.com
#
# Public URLs (Rust, compile-time). Never commit production hosts as source defaults.
# Release workflow injects GitHub secrets ZYNC_SHARE_API_BASE and ZYNC_SHARE_RELAY_URL.
# Debug/dev without these uses http://127.0.0.1:8080 and :8081 (local zync-share).
# ZYNC_SHARE_API_BASE=http://127.0.0.1:8080
# ZYNC_SHARE_RELAY_URL=http://127.0.0.1:8081
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ jobs:
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
# Baked into the Vite bundle for survey/feedback API (prod).
VITE_SURVEY_API_URL: ${{ secrets.VITE_SURVEY_API_URL }}
# Baked into the Rust binary at compile time. Do not hardcode production
# Public URLs hosts in source. Set these repo secrets before tagging.
ZYNC_SHARE_API_BASE: ${{ secrets.ZYNC_SHARE_API_BASE }}
ZYNC_SHARE_RELAY_URL: ${{ secrets.ZYNC_SHARE_RELAY_URL }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
Expand Down
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to Zync are documented in this file. The format is based on

## [Unreleased]

## [2.27.1] - 2026-08-29

### Added
- **Public URLs (Beta)**: Share a local port on the internet with an HTTPS link via Zync account sign-in (GitHub/Google). Separate from Google Drive Sync and from SSH port forwarding. Includes create/stop/start/delete, copy URL, local share agent, OAuth cancel/retry, Beta badge, and bug-report link. Profile avatar prefers the Zync account photo when signed in. ([b44475a], [d826b82], [97ccad1], [c8da354])

### Changed
- **Port Forwarding list**: Compact table grouped by SSH host, with a matching quiet grid. Search, status/type filters, host icons, persisted list/grid, and Copy/Open on the row. ([1203446], [d2d12e1], [72d839b])

### Fixed
- **Remote port conflict**: Busy SSH `-R` listen ports use the same next-port / manual picker as local forwards. If every probe fails, the original reject is kept. Probe binds that cannot be cancelled stay tracked until disconnect. ([9acc835], [72d839b])
- **Public URLs WebSocket**: Forward pre-close request bytes before later frames so upgrade payloads stay in order. ([4784865])
- **Public URLs build config**: Production API/relay hosts are no longer hardcoded. Release builds bake `ZYNC_SHARE_API_BASE` and `ZYNC_SHARE_RELAY_URL` from GitHub Actions secrets. Debug builds may use local `zync-share` loopback.

## [2.26.1] - 2026-08-28

### Fixed
Expand Down Expand Up @@ -1301,9 +1314,19 @@ Partial draft: desktop builds, AppImage Wayland strip, and APT `2.25.4` publishe
[d3f4060]: https://github.com/zync-sh/zync/commit/d3f4060
[840afc2]: https://github.com/zync-sh/zync/commit/840afc2
[193f568]: https://github.com/zync-sh/zync/commit/193f568
[Unreleased]: https://github.com/zync-sh/zync/compare/v2.26.1...HEAD
[Unreleased]: https://github.com/zync-sh/zync/compare/v2.27.1...HEAD
[2.27.1]: https://github.com/zync-sh/zync/compare/v2.26.1...v2.27.1
[2.26.1]: https://github.com/zync-sh/zync/compare/v2.26.0...v2.26.1
[2.26.0]: https://github.com/zync-sh/zync/compare/v2.25.8...v2.26.0
[b44475a]: https://github.com/zync-sh/zync/commit/b44475a
[d826b82]: https://github.com/zync-sh/zync/commit/d826b82
[97ccad1]: https://github.com/zync-sh/zync/commit/97ccad1
[c8da354]: https://github.com/zync-sh/zync/commit/c8da354
[1203446]: https://github.com/zync-sh/zync/commit/1203446
[d2d12e1]: https://github.com/zync-sh/zync/commit/d2d12e1
[72d839b]: https://github.com/zync-sh/zync/commit/72d839b
[9acc835]: https://github.com/zync-sh/zync/commit/9acc835
[4784865]: https://github.com/zync-sh/zync/commit/4784865
[2.25.8]: https://github.com/zync-sh/zync/compare/v2.25.7...v2.25.8
[2.25.7]: https://github.com/zync-sh/zync/compare/v2.25.6...v2.25.7
[2.25.6]: https://github.com/zync-sh/zync/compare/v2.25.5...v2.25.6
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Thank you for your interest in contributing to Zync. This document provides guid

4. Run `npm install` and `npm run tauri dev` to start the development environment.

**Public URLs / survey hosts:** do not hardcode production API URLs in source. Debug builds may talk to local `zync-share` / `zync-survey`. Release builds must bake `ZYNC_SHARE_API_BASE`, `ZYNC_SHARE_RELAY_URL`, and `VITE_SURVEY_API_URL` from GitHub Actions secrets.

**Staying in sync:** Before starting new work, pull the latest from upstream:
```bash
git fetch upstream
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Operator details: [`packaging/arch/README.md`](packaging/arch/README.md).

| Document | Covers |
|----------|--------|
| [docs/SECURITY.md](docs/SECURITY.md) | Vault, sync, OAuth, and operator security guidance |
| [docs/SECURITY.md](docs/SECURITY.md) | Vault, sync, Public URLs, OAuth, and operator security guidance |
| [docs/VAULT.md](docs/VAULT.md) | Vault, credentials, Google sync |
| [docs/TERMINAL.md](docs/TERMINAL.md) | Terminal system (IPC, renderer, lifecycle, settings) |
| [docs/TERMINAL_GHOST.md](docs/TERMINAL_GHOST.md) | Ghost completions (inline, history, paths, suggestion engine) |
Expand Down
35 changes: 29 additions & 6 deletions docs/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Security Notes

**Last updated:** 2026-07-01
**Scope:** Vault, Google sync, credential handling, and operator guidance for current Zync releases.
**Last updated:** 2026-08-29
**Scope:** Vault, Google Drive sync, Public URLs (Beta), credential handling, and operator guidance for current Zync releases.

---

## Summary

Zync's main security surface is **encrypted local vaulting**, optional **remember-on-device unlock**, and **Google Drive–backed encrypted sync**. This document explains how those systems behave, what users and operators should know, and what remains out of scope.
Zync’s core security surface is **encrypted local vaulting**, optional **remember-on-device unlock**, and **Google Drive–backed encrypted sync**. From v2.27.1, an optional **Public URLs (Beta)** feature adds a separate Zync account (GitHub/Google share login) and a localhost-only share agent that talks to Zync-operated API and relay hosts. Zync does not automatically send SSH sessions, vault secrets, or terminal content to those hosts. HTTP, WebSocket, and TCP bytes from the **user-selected shared loopback port** are proxied through the relay while the share is active. This document explains how those systems behave, what users and operators should know, and what remains out of scope.

---

Expand Down Expand Up @@ -36,6 +36,17 @@ Zync's main security surface is **encrypted local vaulting**, optional **remembe
- Sync collections use a **separate encryption passphrase** (local-vault-derived or custom).
- OAuth uses Google's installed/desktop app flow with scoped access to Drive app data and account email.

### Public URLs (Beta)

The Public URLs feature is **not** SSH port forwarding (`-L` / `-R` / `-D`). It is an optional SaaS surface: a Zync account, a desktop share agent, and Zync-operated API + HTTPS/WSS relay.

- **Separate OAuth clients.** Drive Sync login is not Public URLs sign-in. GitHub/Google used for Public URLs are share-account clients (email/profile), not `drive.appdata`.
- **Localhost-only agent.** The desktop agent proxies only to loopback targets (`127.0.0.1` / `localhost` / `::1`). Non-loopback hosts are refused.
- **Link access.** Anyone with the public HTTPS URL can reach that local port while the share is active, unless you set an optional share password. Treat the URL like a capability.
- **Lifetime.** A share stays up while the **share agent** is running on this device. Dropping an SSH session does not stop a Public URL (and vice versa). Stop or delete the share (or sign out) to end it.
- **Tokens.** Share access/refresh material is stored in the OS keyring. Sign out clears the local session.
- **Beta.** Quota is a hard cap (no free/pro copy in-app). Report issues with the in-app bug link after sign-in.

---

## Security Hardening
Expand All @@ -47,7 +58,8 @@ Zync's main security surface is **encrypted local vaulting**, optional **remembe
| **Tab open behavior** | Vault-backed connections defer auto-connect until explicit user reconnect |
| **Sync durability** | Atomic JSON writes with fsync on production sync/vault paths; improved restore convergence and Windows finalize handling |
| **Concurrent operations** | Guards against vault/sync state loss during overlapping provider and local operations |
| **Build-time secret filtering** | `build.rs` blocks most sensitive env keys from compile-time embedding; `GOOGLE_CLIENT_SECRET` is explicitly allowlisted only for desktop OAuth compatibility |
| **Build-time secret filtering** | `build.rs` blocks most sensitive env keys from compile-time embedding; Drive `GOOGLE_CLIENT_SECRET` is explicitly allowlisted only for desktop OAuth compatibility |
| **Public URLs agent** | Share proxy refuses non-loopback targets; OAuth callback binds loopback only |

---

Expand All @@ -67,15 +79,23 @@ Zync's main security surface is **encrypted local vaulting**, optional **remembe
- This trades convenience for risk: anyone with access to your unlocked OS session may access vault-backed connections without re-entering the passphrase until cache expiry or **Forget device**.
- Do **not** enable remember-on-device on shared or untrusted machines.

### Google OAuth
### Google OAuth (Drive Sync)

- Official release builds embed **`GOOGLE_CLIENT_ID`** (and optionally **`GOOGLE_CLIENT_SECRET`**) for the Zync desktop OAuth client.
- Official release builds embed **`GOOGLE_CLIENT_ID`** (and optionally **`GOOGLE_CLIENT_SECRET`**) for the **Drive Sync** desktop OAuth client.
- **Low risk, expected for desktop apps:** Google's installed-app model does not treat the client secret as confidential — it cannot be kept secret inside a distributed binary. This is normal for desktop OAuth and is **not** the same as leaking a server-side OAuth secret.
- User data access still requires **per-user consent** and scoped tokens; extracting the embedded client pair alone does not grant access to someone else's Google data.
- Do **not** reuse a production **web/server** OAuth client for Zync desktop builds.
- Do **not** reuse the Drive Sync client for Public URLs sign-in (and vice versa).
- Google sync tokens are stored locally; disconnect/revoke flows clear provider tokens where implemented.
- A future **PKCE-only** client (no embedded secret) is planned as hygiene improvement, not an urgent security blocker.

### Public URLs (Beta) operator notes

- Treat an active Public URL as **internet exposure of that loopback port** via Zync’s relay.
- Do not share ports that bind privileged or sensitive local services unless you intend that exposure.
- Stop/delete the share when finished. Signing out of Zync ends the local agent session; revoke GitHub/Google app access if you want the account unlinked at the provider.
- Survey / Settings → Feedback POSTs (when used) go to a Zync-operated survey API. They are optional and do not include vault secrets, SSH keys, or terminal contents.

### Backups and restore

- Treat Google Drive sync collections as **encrypted backups**, not a live shared secrets broker.
Expand All @@ -91,6 +111,7 @@ These are **product scope** limits today, not vulnerabilities:
- **No team/org policy controls** — vault and sync are single-user oriented; shared/team vaults are deferred to later phases.
- **No live bi-directional sync scheduling** — Google sync is manual upload/restore; there is no background auto-sync scheduler yet.
- **Plugins** — marketplace plugins do **not** receive raw vault secrets by design; only explicit future export/copy flows could change that.
- **Public URLs Beta** — no team/org sharing, no custom domains, no pricing plans in-app; GA hardening is deferred.

### AI credential policy

Expand All @@ -112,4 +133,6 @@ If you discover a vulnerability in Zync, report it privately to the maintainers

- [VAULT.md](./VAULT.md) — vault and sync architecture
- [VAULT_ROADMAP.md](./VAULT_ROADMAP.md) — planned vault/sync work
- [TUNNELS.md](./TUNNELS.md) — SSH port forwarding (separate from Public URLs)
- [CHANGELOG.md](../CHANGELOG.md) — release history
- Privacy Policy (marketing site) — `https://zync.thesudoer.in/privacy`
23 changes: 13 additions & 10 deletions docs/TUNNELS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Port Forwarding & Tunnels — Architecture & Reference

**Last updated:** 2026-07-09
**Last updated:** 2026-08-29
**Applies to:** Zync v2.21.0+
**User-facing guide:** [zync.thesudoer.in/docs/port-forwarding](https://zync.thesudoer.in/docs/port-forwarding)

Expand Down Expand Up @@ -104,9 +104,9 @@ flowchart TB
GL[GlobalTunnelList]
AM[AddTunnelModal]
IM[ImportSSHCommandModal]
TC[TunnelCard]
TM --> TC
GL --> TC
TR[TunnelRow]
TM --> TR
GL --> TR
AM --> TM
AM --> GL
end
Expand Down Expand Up @@ -163,6 +163,7 @@ Equivalent to `ssh -R [bind:]remotePort:localHost:localPort`.

- Requests `tcpip_forward` on the SSH server for `bindAddress:remotePort`.
- Incoming forwarded connections are proxied to the local target (`remoteHost` in the saved config stores the **local target host** for `-R`).
- If the server rejects the listen and a later port on the same bind succeeds, Zync surfaces the same **port conflict** picker as local (suggested next port or manual). If every probe fails, the original rejection is kept.

**Server requirement:** Remote binds on non-loopback addresses need `GatewayPorts` / `AllowTcpForwarding` on `sshd` (documented on the marketing site).

Expand Down Expand Up @@ -213,13 +214,15 @@ Failed restarts on reconnect surface a **toast** per tunnel.

| Surface | Role |
|---------|------|
| **Port Forwarding tab** | Per-host tunnel list; grid/list toggle; start/stop/edit |
| **Global dashboard** | All tunnels across connections; search; grid/list; group collapse |
| **Port Forwarding tab** | Per-host compact table or matching grid tiles; search + status/type filters |
| **Global dashboard** | All tunnels across connections; same views + filters; collapsible **host** groups |
| **Add / edit modal** | Presets, bind address, auto-start, groups |
| **Import modal** | Paste `ssh -L` / `-R` / `-D` command |
| **Tunnel card** | Status, flow line, type badges (incl. SOCKS), action bar |
| **Tunnel row / tile** | Status, name, type (`-L`/`-R`/`-D`), flow, Start/Stop, copy, open, overflow menu |

**Global list grouping:** UI groups by user-defined `group` field, **not** by connection. Connection name appears on each card.
**Global list grouping:** UI groups by **SSH host** (connection). User-defined `group` is a secondary label on the row. The per-host tab still sections by named group when any tunnel on that host has a group.

**Views:** List is default. Grid uses the same type, status, flow, and actions as the list, laid out as quiet tiles. Copy and Open sit on the row/tile; Edit/Delete stay in the overflow menu.

**Parity:** Both surfaces share `tunnelSlice` state. Start/stop from either surface reflects immediately in the other via events.

Expand Down Expand Up @@ -368,7 +371,7 @@ Directional work — not a commitment order.
### Observability

- Bytes in/out per tunnel (optional)
- Last error + uptime on tunnel cards
- Last error + uptime on tunnel rows
- Health probe for HTTP forwards (optional HEAD request)

### UI
Expand All @@ -388,7 +391,7 @@ Directional work — not a commitment order.

```
src/components/tunnel/
TunnelManager.tsx, GlobalTunnelList.tsx, TunnelCard.tsx
TunnelManager.tsx, GlobalTunnelList.tsx, TunnelRow.tsx, TunnelCard.tsx

src/components/modals/
AddTunnelModal.tsx, ImportSSHCommandModal.tsx
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zync",
"private": true,
"version": "2.26.1",
"version": "2.27.1",
"type": "module",
"repository": {
"type": "git",
Expand Down
66 changes: 65 additions & 1 deletion src-tauri/Cargo.lock

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

5 changes: 4 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "zync"
version = "2.26.1"
version = "2.27.1"
description = "A modern SSH client"
authors = ["Gajendra"]
edition = "2021"
Expand Down Expand Up @@ -41,6 +41,9 @@ url = "2.5"
regex = "1.12.3"
tauri-plugin-clipboard-manager = "2.3.2"
reqwest = { version = "0.12", features = ["json", "stream", "multipart"] }
tokio-tungstenite = { version = "0.26", features = ["native-tls"] }
futures-util = { version = "0.3", features = ["sink"] }
bytes = "1"
zip = "2.2"
log = "0.4"
# Vault crypto (Phase 0)
Expand Down
Loading
Loading