gitspace 0.2.0-rc.1
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/.claude/settings.local.json +21 -0
- package/.gitspace/bundle.json +50 -0
- package/.gitspace/select/01-status.sh +40 -0
- package/.gitspace/setup/01-install-deps.sh +12 -0
- package/.gitspace/setup/02-typecheck.sh +16 -0
- package/AGENTS.md +439 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +25 -0
- package/README.md +607 -0
- package/bin/gssh +62 -0
- package/bun.lock +647 -0
- package/docs/CONNECTION.md +623 -0
- package/docs/GATEWAY-WORKER.md +319 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GITSPACE-PLATFORM.md +1819 -0
- package/docs/INFRASTRUCTURE.md +1347 -0
- package/docs/PROTOCOL.md +619 -0
- package/docs/QUICKSTART.md +174 -0
- package/docs/RELAY.md +327 -0
- package/docs/REMOTE-DESIGN.md +549 -0
- package/docs/ROADMAP.md +564 -0
- package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
- package/docs/STACK-DESIGN.md +588 -0
- package/docs/UNIFIED_ARCHITECTURE.md +292 -0
- package/experiments/pty-benchmark.ts +148 -0
- package/experiments/pty-latency.ts +100 -0
- package/experiments/router/client.ts +199 -0
- package/experiments/router/protocol.ts +74 -0
- package/experiments/router/router.ts +217 -0
- package/experiments/router/session.ts +180 -0
- package/experiments/router/test.ts +133 -0
- package/experiments/socket-bandwidth.ts +77 -0
- package/homebrew/gitspace.rb +45 -0
- package/landing-page/ATTRIBUTIONS.md +3 -0
- package/landing-page/README.md +11 -0
- package/landing-page/bun.lock +801 -0
- package/landing-page/guidelines/Guidelines.md +61 -0
- package/landing-page/index.html +37 -0
- package/landing-page/package.json +90 -0
- package/landing-page/postcss.config.mjs +15 -0
- package/landing-page/public/_redirects +1 -0
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +53 -0
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
- package/landing-page/src/app/components/ui/accordion.tsx +66 -0
- package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
- package/landing-page/src/app/components/ui/alert.tsx +66 -0
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
- package/landing-page/src/app/components/ui/avatar.tsx +53 -0
- package/landing-page/src/app/components/ui/badge.tsx +46 -0
- package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
- package/landing-page/src/app/components/ui/button.tsx +57 -0
- package/landing-page/src/app/components/ui/calendar.tsx +75 -0
- package/landing-page/src/app/components/ui/card.tsx +92 -0
- package/landing-page/src/app/components/ui/carousel.tsx +241 -0
- package/landing-page/src/app/components/ui/chart.tsx +353 -0
- package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
- package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
- package/landing-page/src/app/components/ui/command.tsx +177 -0
- package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
- package/landing-page/src/app/components/ui/dialog.tsx +135 -0
- package/landing-page/src/app/components/ui/drawer.tsx +132 -0
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
- package/landing-page/src/app/components/ui/form.tsx +168 -0
- package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
- package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
- package/landing-page/src/app/components/ui/input.tsx +21 -0
- package/landing-page/src/app/components/ui/label.tsx +24 -0
- package/landing-page/src/app/components/ui/menubar.tsx +276 -0
- package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
- package/landing-page/src/app/components/ui/pagination.tsx +127 -0
- package/landing-page/src/app/components/ui/popover.tsx +48 -0
- package/landing-page/src/app/components/ui/progress.tsx +31 -0
- package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
- package/landing-page/src/app/components/ui/resizable.tsx +56 -0
- package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
- package/landing-page/src/app/components/ui/select.tsx +189 -0
- package/landing-page/src/app/components/ui/separator.tsx +28 -0
- package/landing-page/src/app/components/ui/sheet.tsx +139 -0
- package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
- package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
- package/landing-page/src/app/components/ui/slider.tsx +63 -0
- package/landing-page/src/app/components/ui/sonner.tsx +25 -0
- package/landing-page/src/app/components/ui/switch.tsx +31 -0
- package/landing-page/src/app/components/ui/table.tsx +116 -0
- package/landing-page/src/app/components/ui/tabs.tsx +66 -0
- package/landing-page/src/app/components/ui/textarea.tsx +18 -0
- package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
- package/landing-page/src/app/components/ui/toggle.tsx +47 -0
- package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
- package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
- package/landing-page/src/app/components/ui/utils.ts +6 -0
- package/landing-page/src/components/docs/DocsContent.tsx +718 -0
- package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
- package/landing-page/src/components/landing/CTA.tsx +59 -0
- package/landing-page/src/components/landing/Comparison.tsx +84 -0
- package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
- package/landing-page/src/components/landing/Features.tsx +201 -0
- package/landing-page/src/components/landing/Hero.tsx +142 -0
- package/landing-page/src/components/landing/Pricing.tsx +140 -0
- package/landing-page/src/components/landing/Roadmap.tsx +86 -0
- package/landing-page/src/components/landing/Security.tsx +81 -0
- package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
- package/landing-page/src/components/landing/UseCases.tsx +55 -0
- package/landing-page/src/components/landing/Workflow.tsx +101 -0
- package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
- package/landing-page/src/components/layout/Footer.tsx +55 -0
- package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
- package/landing-page/src/components/ui/badge.tsx +39 -0
- package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
- package/landing-page/src/components/ui/button.tsx +57 -0
- package/landing-page/src/components/ui/card.tsx +79 -0
- package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
- package/landing-page/src/components/ui/separator.tsx +28 -0
- package/landing-page/src/lib/utils.ts +6 -0
- package/landing-page/src/main.tsx +10 -0
- package/landing-page/src/pages/Dashboard.tsx +133 -0
- package/landing-page/src/pages/DocsPage.tsx +79 -0
- package/landing-page/src/pages/LandingPage.tsx +31 -0
- package/landing-page/src/pages/TerminalView.tsx +106 -0
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +3 -0
- package/landing-page/src/styles/tailwind.css +4 -0
- package/landing-page/src/styles/theme.css +181 -0
- package/landing-page/vite.config.ts +19 -0
- package/npm/darwin-arm64/bin/gssh +0 -0
- package/npm/darwin-arm64/package.json +20 -0
- package/package.json +74 -0
- package/scripts/build.ts +284 -0
- package/scripts/release.ts +140 -0
- package/src/__tests__/test-utils.ts +298 -0
- package/src/commands/__tests__/serve-messages.test.ts +190 -0
- package/src/commands/access.ts +298 -0
- package/src/commands/add.ts +452 -0
- package/src/commands/auth.ts +364 -0
- package/src/commands/connect.ts +287 -0
- package/src/commands/directory.ts +16 -0
- package/src/commands/host.ts +396 -0
- package/src/commands/identity.ts +184 -0
- package/src/commands/list.ts +200 -0
- package/src/commands/relay.ts +315 -0
- package/src/commands/remove.ts +241 -0
- package/src/commands/serve.ts +1493 -0
- package/src/commands/share.ts +456 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/switch.ts +353 -0
- package/src/commands/tmux.ts +317 -0
- package/src/core/__tests__/access.test.ts +240 -0
- package/src/core/access.ts +277 -0
- package/src/core/bundle.ts +342 -0
- package/src/core/config.ts +510 -0
- package/src/core/git.ts +317 -0
- package/src/core/github.ts +151 -0
- package/src/core/identity.ts +631 -0
- package/src/core/linear.ts +225 -0
- package/src/core/shell.ts +161 -0
- package/src/core/trusted-relays.ts +315 -0
- package/src/index.ts +821 -0
- package/src/lib/remote-session/index.ts +7 -0
- package/src/lib/remote-session/protocol.ts +267 -0
- package/src/lib/remote-session/session-handler.ts +581 -0
- package/src/lib/remote-session/workspace-scanner.ts +167 -0
- package/src/lib/tmux-lite/README.md +81 -0
- package/src/lib/tmux-lite/cli.ts +796 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
- package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
- package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
- package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
- package/src/lib/tmux-lite/crypto/frames.ts +141 -0
- package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
- package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
- package/src/lib/tmux-lite/crypto/identity.ts +286 -0
- package/src/lib/tmux-lite/crypto/index.ts +51 -0
- package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
- package/src/lib/tmux-lite/crypto/invites.ts +215 -0
- package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
- package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
- package/src/lib/tmux-lite/crypto/keys.ts +47 -0
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
- package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
- package/src/lib/tmux-lite/handshake-handler.ts +451 -0
- package/src/lib/tmux-lite/protocol.test.ts +307 -0
- package/src/lib/tmux-lite/protocol.ts +266 -0
- package/src/lib/tmux-lite/relay-client.ts +506 -0
- package/src/lib/tmux-lite/server.ts +1250 -0
- package/src/lib/tmux-lite/shell-integration.sh +37 -0
- package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
- package/src/lib/tmux-lite/terminal-queries.ts +49 -0
- package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
- package/src/relay/__tests__/helpers/auth.ts +354 -0
- package/src/relay/__tests__/helpers/ports.ts +51 -0
- package/src/relay/__tests__/protocol-validation.test.ts +265 -0
- package/src/relay/authorization.ts +303 -0
- package/src/relay/embedded-assets.generated.d.ts +15 -0
- package/src/relay/identity.ts +352 -0
- package/src/relay/index.ts +57 -0
- package/src/relay/pipes.test.ts +427 -0
- package/src/relay/pipes.ts +195 -0
- package/src/relay/protocol.ts +804 -0
- package/src/relay/registries.test.ts +437 -0
- package/src/relay/registries.ts +593 -0
- package/src/relay/server.test.ts +1323 -0
- package/src/relay/server.ts +1092 -0
- package/src/relay/signing.ts +238 -0
- package/src/relay/types.ts +69 -0
- package/src/serve/client-session-manager.ts +622 -0
- package/src/serve/daemon.ts +497 -0
- package/src/serve/pty-session.ts +236 -0
- package/src/serve/types.ts +169 -0
- package/src/shared/components/Flow.tsx +453 -0
- package/src/shared/components/Flow.tui.tsx +343 -0
- package/src/shared/components/Flow.web.tsx +442 -0
- package/src/shared/components/Inbox.tsx +446 -0
- package/src/shared/components/Inbox.tui.tsx +262 -0
- package/src/shared/components/Inbox.web.tsx +329 -0
- package/src/shared/components/MachineList.tsx +187 -0
- package/src/shared/components/MachineList.tui.tsx +161 -0
- package/src/shared/components/MachineList.web.tsx +210 -0
- package/src/shared/components/ProjectList.tsx +176 -0
- package/src/shared/components/ProjectList.tui.tsx +109 -0
- package/src/shared/components/ProjectList.web.tsx +143 -0
- package/src/shared/components/SpacesBrowser.tsx +332 -0
- package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
- package/src/shared/components/SpacesBrowser.web.tsx +221 -0
- package/src/shared/components/index.ts +103 -0
- package/src/shared/hooks/index.ts +16 -0
- package/src/shared/hooks/useNavigation.ts +226 -0
- package/src/shared/index.ts +122 -0
- package/src/shared/providers/LocalMachineProvider.ts +425 -0
- package/src/shared/providers/MachineProvider.ts +165 -0
- package/src/shared/providers/RemoteMachineProvider.ts +444 -0
- package/src/shared/providers/index.ts +26 -0
- package/src/shared/types.ts +145 -0
- package/src/tui/adapters.ts +120 -0
- package/src/tui/app.tsx +1816 -0
- package/src/tui/components/Terminal.tsx +580 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useAppState.ts +314 -0
- package/src/tui/hooks/useDaemonStatus.ts +174 -0
- package/src/tui/hooks/useInboxTUI.ts +113 -0
- package/src/tui/hooks/useRemoteMachines.ts +209 -0
- package/src/tui/index.ts +24 -0
- package/src/tui/state.ts +299 -0
- package/src/tui/terminal-bracketed-paste.test.ts +45 -0
- package/src/tui/terminal-bracketed-paste.ts +47 -0
- package/src/types/bundle.ts +112 -0
- package/src/types/config.ts +89 -0
- package/src/types/errors.ts +206 -0
- package/src/types/identity.ts +284 -0
- package/src/types/workspace-fuzzy.ts +49 -0
- package/src/types/workspace.ts +151 -0
- package/src/utils/bun-socket-writer.ts +80 -0
- package/src/utils/deps.ts +127 -0
- package/src/utils/fuzzy-match.ts +125 -0
- package/src/utils/logger.ts +127 -0
- package/src/utils/markdown.ts +254 -0
- package/src/utils/onboarding.ts +229 -0
- package/src/utils/prompts.ts +114 -0
- package/src/utils/run-commands.ts +112 -0
- package/src/utils/run-scripts.ts +142 -0
- package/src/utils/sanitize.ts +98 -0
- package/src/utils/secrets.ts +122 -0
- package/src/utils/shell-escape.ts +40 -0
- package/src/utils/utf8.ts +79 -0
- package/src/utils/workspace-state.ts +47 -0
- package/src/web/README.md +73 -0
- package/src/web/bun.lock +575 -0
- package/src/web/eslint.config.js +23 -0
- package/src/web/index.html +16 -0
- package/src/web/package.json +37 -0
- package/src/web/public/vite.svg +1 -0
- package/src/web/src/App.tsx +604 -0
- package/src/web/src/assets/react.svg +1 -0
- package/src/web/src/components/Terminal.tsx +207 -0
- package/src/web/src/hooks/useRelayConnection.ts +224 -0
- package/src/web/src/hooks/useTerminal.ts +699 -0
- package/src/web/src/index.css +55 -0
- package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
- package/src/web/src/lib/crypto/frames.ts +205 -0
- package/src/web/src/lib/crypto/handshake.ts +396 -0
- package/src/web/src/lib/crypto/identity.ts +128 -0
- package/src/web/src/lib/crypto/keyexchange.ts +246 -0
- package/src/web/src/lib/crypto/relay-signing.ts +53 -0
- package/src/web/src/lib/invite.ts +58 -0
- package/src/web/src/lib/storage/identity-store.ts +94 -0
- package/src/web/src/main.tsx +10 -0
- package/src/web/src/types/identity.ts +45 -0
- package/src/web/tsconfig.app.json +28 -0
- package/src/web/tsconfig.json +7 -0
- package/src/web/tsconfig.node.json +26 -0
- package/src/web/vite.config.ts +31 -0
- package/todo-security.md +92 -0
- package/tsconfig.json +23 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
- package/worker/bun.lock +237 -0
- package/worker/package.json +22 -0
- package/worker/schema.sql +96 -0
- package/worker/src/handlers/auth.ts +451 -0
- package/worker/src/handlers/subdomains.ts +376 -0
- package/worker/src/handlers/user.ts +98 -0
- package/worker/src/index.ts +70 -0
- package/worker/src/middleware/auth.ts +152 -0
- package/worker/src/services/cloudflare.ts +609 -0
- package/worker/src/types.ts +96 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
# GitSpace Remote Access Design
|
|
2
|
+
|
|
3
|
+
This document describes the architecture for GitSpace remote access - enabling users to securely access their terminal sessions from any device using end-to-end encryption.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
GitSpace enables developers to run persistent terminal sessions on their local machine and access them remotely from any browser or CLI client. The system uses **identity-based access control** with **X3DH key exchange** for end-to-end encryption, ensuring that even the relay service cannot read terminal content.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ GITSPACE REMOTE ACCESS │
|
|
12
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
13
|
+
│ │
|
|
14
|
+
│ LOCAL MACHINE RELAY REMOTE ACCESS │
|
|
15
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
16
|
+
│ │ gssh serve │ │ WebSocket relay │ │ Browser │ │
|
|
17
|
+
│ │ └─ tmux-lite │◀═════▶│ (blind router) │◀══════▶│ CLI │ │
|
|
18
|
+
│ │ server │ E2E │ │ E2E │ TUI │ │
|
|
19
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
|
20
|
+
│ │
|
|
21
|
+
│ Ed25519/X25519 keys Routes encrypted X3DH handshake │
|
|
22
|
+
│ stored locally bytes only per connection │
|
|
23
|
+
│ │
|
|
24
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Security Model
|
|
28
|
+
|
|
29
|
+
### Identity-Based Access
|
|
30
|
+
|
|
31
|
+
Unlike password-based systems, GitSpace uses cryptographic identities for access control:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
35
|
+
│ IDENTITY-BASED ACCESS CONTROL │
|
|
36
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
37
|
+
│ │
|
|
38
|
+
│ Each machine/client has an IDENTITY: │
|
|
39
|
+
│ ├── Ed25519 signing keypair (proves identity) │
|
|
40
|
+
│ ├── X25519 key exchange keypair (establishes encryption) │
|
|
41
|
+
│ └── Unique identifier (derived from signing public key) │
|
|
42
|
+
│ │
|
|
43
|
+
│ Access is granted by PUBLIC KEY, not passwords: │
|
|
44
|
+
│ • Machine owner runs: gssh access add <client-public-key> │
|
|
45
|
+
│ • Client can now connect and perform X3DH handshake │
|
|
46
|
+
│ • No shared secrets needed - cryptographic proof of identity │
|
|
47
|
+
│ │
|
|
48
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Two-Layer Security
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ Layer 1: RELAY AUTHENTICATION │
|
|
56
|
+
│ ───────────────────────────── │
|
|
57
|
+
│ │
|
|
58
|
+
│ • Pre-authorized machine keys + challenge-response │
|
|
59
|
+
│ • Signed client messages bind identity IDs │
|
|
60
|
+
│ • Validates WHO can connect to relay │
|
|
61
|
+
│ • Enables routing, machine registry, access lists │
|
|
62
|
+
│ │
|
|
63
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
64
|
+
│ Layer 2: E2E ENCRYPTION (X3DH) │
|
|
65
|
+
│ ───────────────────────────── │
|
|
66
|
+
│ │
|
|
67
|
+
│ • X3DH handshake per connection │
|
|
68
|
+
│ • Client proves identity via Ed25519 signature │
|
|
69
|
+
│ • Machine verifies client is in access list │
|
|
70
|
+
│ • Session keys derived via HKDF │
|
|
71
|
+
│ • All terminal I/O encrypted with AES-256-GCM │
|
|
72
|
+
│ │
|
|
73
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Access Types
|
|
77
|
+
|
|
78
|
+
| Access Type | Description | Capabilities |
|
|
79
|
+
|-------------|-------------|--------------|
|
|
80
|
+
| `full` | Permanent access grant | Browse all projects/workspaces, create/attach/kill sessions, manage access |
|
|
81
|
+
| `session-invite` | One-time session access | View specific session only, no browsing, read-only |
|
|
82
|
+
|
|
83
|
+
### What the Relay Can See
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
87
|
+
│ RELAY VISIBILITY │
|
|
88
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
89
|
+
│ │
|
|
90
|
+
│ ✓ VISIBLE (metadata for routing): ✗ HIDDEN (encrypted): │
|
|
91
|
+
│ • Machine ID • Terminal content │
|
|
92
|
+
│ • Client identity ID • Keystrokes │
|
|
93
|
+
│ • Connection timestamps • Commands and output │
|
|
94
|
+
│ • Data volume (bytes) • File contents │
|
|
95
|
+
│ • Online/offline status • Session names │
|
|
96
|
+
│ • Access list (public keys only) • Workspace information │
|
|
97
|
+
│ │
|
|
98
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Cryptographic Protocol
|
|
104
|
+
|
|
105
|
+
### Primitives
|
|
106
|
+
|
|
107
|
+
| Primitive | Algorithm | Use |
|
|
108
|
+
|-----------|-----------|-----|
|
|
109
|
+
| Identity Signing | Ed25519 | Prove identity, sign challenges |
|
|
110
|
+
| Key Exchange | X25519 | ECDH for shared secrets |
|
|
111
|
+
| Password KDF | Scrypt (N=2^15, r=8, p=1) | Encrypt keypair at rest |
|
|
112
|
+
| Session Key Derivation | HKDF-SHA256 | Derive session keys from X3DH |
|
|
113
|
+
| Symmetric Encryption | AES-256-GCM | Frame encryption |
|
|
114
|
+
| Nonce | 12 bytes random | Per-frame freshness |
|
|
115
|
+
| Relay Message Signing | Ed25519 | Machine challenge + client messages |
|
|
116
|
+
|
|
117
|
+
### X3DH Handshake
|
|
118
|
+
|
|
119
|
+
After a client connects through the relay, X3DH establishes session encryption:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
┌───────────────────────────────────────────────────────────────────────────┐
|
|
123
|
+
│ X3DH KEY EXCHANGE │
|
|
124
|
+
├───────────────────────────────────────────────────────────────────────────┤
|
|
125
|
+
│ │
|
|
126
|
+
│ CLIENT MACHINE │
|
|
127
|
+
│ ────── ─────── │
|
|
128
|
+
│ │
|
|
129
|
+
│ 1. Generate ephemeral X25519 keypair │
|
|
130
|
+
│ │
|
|
131
|
+
│ 2. Send client_hello: │
|
|
132
|
+
│ ├── clientIdentityKey (X25519) ───────────────────────────────▶ │
|
|
133
|
+
│ └── clientEphemeralKey (X25519) │
|
|
134
|
+
│ │
|
|
135
|
+
│ 3. Verify client in access list │
|
|
136
|
+
│ │
|
|
137
|
+
│ 4. Generate ephemeral keypair │
|
|
138
|
+
│ │
|
|
139
|
+
│ 5. Receive server_hello: ◀─────────────────────────────── │
|
|
140
|
+
│ ├── serverIdentityKey (X25519) │
|
|
141
|
+
│ ├── serverEphemeralKey (X25519) │
|
|
142
|
+
│ └── serverSigningKey (Ed25519) │
|
|
143
|
+
│ │
|
|
144
|
+
│ 6. Compute shared secrets: 6. Compute shared secrets: │
|
|
145
|
+
│ DH1 = ephemeral × serverIdentity DH1 = identity × clientEphem │
|
|
146
|
+
│ DH2 = ephemeral × serverEphemeral DH2 = ephemeral × clientEphem │
|
|
147
|
+
│ DH3 = identity × serverEphemeral DH3 = ephemeral × clientIdent │
|
|
148
|
+
│ │
|
|
149
|
+
│ 7. Sign transcript ◀─────────────────────────────── │
|
|
150
|
+
│ Send client_auth: │
|
|
151
|
+
│ ├── Ed25519 signature │
|
|
152
|
+
│ ├── signingPublicKey │
|
|
153
|
+
│ └── inviteToken (if via invite) │
|
|
154
|
+
│ │
|
|
155
|
+
│ 8. Verify signature │
|
|
156
|
+
│ 9. Check access list again │
|
|
157
|
+
│ │
|
|
158
|
+
│ 10. Receive server_auth: ◀─────────────────────────────── │
|
|
159
|
+
│ ├── Ed25519 signature │
|
|
160
|
+
│ └── accessType (full/session-invite) │
|
|
161
|
+
│ │
|
|
162
|
+
│ 11. Both derive session keys via HKDF │
|
|
163
|
+
│ ├── sendKey (client → machine) │
|
|
164
|
+
│ ├── receiveKey (machine → client) │
|
|
165
|
+
│ └── sessionId (for correlation) │
|
|
166
|
+
│ │
|
|
167
|
+
│ ════════════════════ ENCRYPTED CHANNEL ESTABLISHED ═══════════════════ │
|
|
168
|
+
│ │
|
|
169
|
+
└───────────────────────────────────────────────────────────────────────────┘
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Key Derivation
|
|
173
|
+
|
|
174
|
+
**Two algorithms are used for different purposes:**
|
|
175
|
+
|
|
176
|
+
1. **Scrypt** - For password-based encryption of the identity keypair file
|
|
177
|
+
2. **HKDF-SHA256** - For deriving session keys from X3DH shared secrets
|
|
178
|
+
|
|
179
|
+
#### Password-Based Key Derivation (Scrypt)
|
|
180
|
+
|
|
181
|
+
The identity keypair is encrypted at rest using scrypt:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { scrypt } from 'node:crypto';
|
|
185
|
+
|
|
186
|
+
// Scrypt parameters (strong defaults)
|
|
187
|
+
const N = 2 ** 15; // CPU/memory cost
|
|
188
|
+
const r = 8; // Block size
|
|
189
|
+
const p = 1; // Parallelization
|
|
190
|
+
|
|
191
|
+
// Derive encryption key from password
|
|
192
|
+
const salt = randomBytes(16);
|
|
193
|
+
const key = scrypt(password, salt, 32, { N, r, p });
|
|
194
|
+
|
|
195
|
+
// Encrypt keypair with AES-256-GCM using derived key
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Session Key Derivation (HKDF-SHA256)
|
|
199
|
+
|
|
200
|
+
After X3DH handshake, session keys are derived using HKDF:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { hkdf } from '@noble/hashes/hkdf';
|
|
204
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
205
|
+
|
|
206
|
+
// After X3DH, both sides have shared secret material
|
|
207
|
+
const sharedSecret = concat(DH1, DH2, DH3); // 96 bytes
|
|
208
|
+
const transcript = concat(clientHello, serverHello);
|
|
209
|
+
|
|
210
|
+
// Derive session keys using HKDF with domain separation
|
|
211
|
+
const masterSecret = hkdf(sha256, sharedSecret, transcript, 'spaces-v1-master', 32);
|
|
212
|
+
const sendKey = hkdf(sha256, masterSecret, 'send', 'spaces-v1-send', 32);
|
|
213
|
+
const receiveKey = hkdf(sha256, masterSecret, 'recv', 'spaces-v1-receive', 32);
|
|
214
|
+
const sessionId = hkdf(sha256, masterSecret, 'session', 'spaces-v1-session-id', 16);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Encrypted Frame Format
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
┌────────────────────────────────────────────────────────────────────────────┐
|
|
221
|
+
│ Encrypted Frame Structure │
|
|
222
|
+
├────────────────────────────────────────────────────────────────────────────┤
|
|
223
|
+
│ │
|
|
224
|
+
│ Bytes 0-3: Stream ID (4 bytes, big-endian) │
|
|
225
|
+
│ Bytes 4-15: Nonce (12 bytes, random) │
|
|
226
|
+
│ Bytes 16+: Ciphertext (AES-256-GCM) │
|
|
227
|
+
│ ├── Encrypted payload │
|
|
228
|
+
│ └── 16-byte authentication tag (appended) │
|
|
229
|
+
│ │
|
|
230
|
+
│ Minimum frame: 32 bytes (4 + 12 + 16) │
|
|
231
|
+
│ │
|
|
232
|
+
│ Stream IDs: │
|
|
233
|
+
│ • 0: Master stream (full machine access) │
|
|
234
|
+
│ • 1+: Reserved for future share streams │
|
|
235
|
+
│ │
|
|
236
|
+
└────────────────────────────────────────────────────────────────────────────┘
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Components
|
|
242
|
+
|
|
243
|
+
### 1. Machine Daemon (`gssh serve`)
|
|
244
|
+
|
|
245
|
+
**Location:** User's local machine
|
|
246
|
+
**Role:** Session management, PTY handling, encryption endpoint
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
src/
|
|
250
|
+
├── commands/serve.ts # CLI command, daemon entry
|
|
251
|
+
├── serve/
|
|
252
|
+
│ ├── daemon.ts # PID/socket management, status server
|
|
253
|
+
│ ├── client-session-manager.ts # Handle client connections
|
|
254
|
+
│ └── pty-session.ts # PTY session lifecycle
|
|
255
|
+
└── lib/tmux-lite/
|
|
256
|
+
├── server.ts # Session manager with xterm-headless
|
|
257
|
+
├── handshake-handler.ts # X3DH implementation
|
|
258
|
+
├── relay-client.ts # WebSocket connection to relay
|
|
259
|
+
└── crypto/ # Encryption primitives
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Key responsibilities:
|
|
263
|
+
- Maintain persistent connection to relay
|
|
264
|
+
- Handle X3DH handshakes with clients
|
|
265
|
+
- Manage PTY sessions via xterm-headless
|
|
266
|
+
- Encrypt all outbound terminal data
|
|
267
|
+
- Track access list and verify clients
|
|
268
|
+
|
|
269
|
+
### 2. Relay Server
|
|
270
|
+
|
|
271
|
+
**Location:** gitspace.sh cloud (or self-hosted)
|
|
272
|
+
**Role:** Authentication, routing, blind relay
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
src/relay/
|
|
276
|
+
├── server.ts # WebSocket routing server
|
|
277
|
+
├── protocol.ts # Message type definitions
|
|
278
|
+
├── registries.ts # Machine/client/access registries
|
|
279
|
+
├── authorization.ts # Relay authorization helpers
|
|
280
|
+
└── types.ts # Type definitions
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Key responsibilities:
|
|
284
|
+
- Authenticate machines (Ed25519 challenge-response)
|
|
285
|
+
- Verify signed client messages
|
|
286
|
+
- Route encrypted bytes between parties
|
|
287
|
+
- Maintain machine registry (online/offline)
|
|
288
|
+
- Broadcast global access list updates
|
|
289
|
+
|
|
290
|
+
### 3. Web Client
|
|
291
|
+
|
|
292
|
+
**Location:** Browser
|
|
293
|
+
**Role:** User interface, decryption endpoint
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
src/web/src/
|
|
297
|
+
├── App.tsx # Main application
|
|
298
|
+
├── hooks/
|
|
299
|
+
│ ├── useRelayConnection.ts # WebSocket + machine list
|
|
300
|
+
│ └── useTerminal.ts # Terminal session management
|
|
301
|
+
├── components/
|
|
302
|
+
│ └── Terminal.tsx # xterm.js wrapper
|
|
303
|
+
└── lib/crypto/ # Client-side X3DH + encryption
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Key responsibilities:
|
|
307
|
+
- Connect to relay and sign routing messages
|
|
308
|
+
- Perform X3DH handshake with machine
|
|
309
|
+
- Decrypt and render terminal output
|
|
310
|
+
- Encrypt and send user input
|
|
311
|
+
|
|
312
|
+
### 4. CLI/TUI Client
|
|
313
|
+
|
|
314
|
+
**Location:** User's device (terminal)
|
|
315
|
+
**Role:** Native terminal access
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
src/
|
|
319
|
+
├── commands/connect.ts # CLI connect command
|
|
320
|
+
├── tui/
|
|
321
|
+
│ ├── app.tsx # TUI application
|
|
322
|
+
│ ├── adapters.ts # Machine provider adapters
|
|
323
|
+
│ └── hooks/useRemoteMachines.ts
|
|
324
|
+
└── shared/providers/
|
|
325
|
+
└── RemoteMachineProvider.ts
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Access Control
|
|
331
|
+
|
|
332
|
+
### Granting Access
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
# On the machine owner's side
|
|
336
|
+
$ gssh access add gssh_pk_abc123...
|
|
337
|
+
|
|
338
|
+
✓ Added client: gssh_pk_abc123...
|
|
339
|
+
|
|
340
|
+
Label (optional): Brad's Phone
|
|
341
|
+
Access type: full
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Access List Storage
|
|
345
|
+
|
|
346
|
+
Access entries are stored locally on the machine:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
interface AccessEntry {
|
|
350
|
+
clientIdentityId: string; // Derived from public key
|
|
351
|
+
signingPublicKey: string; // Ed25519 public key (base64)
|
|
352
|
+
keyExchangePublicKey: string; // X25519 public key (base64)
|
|
353
|
+
label?: string; // Human-readable name
|
|
354
|
+
grantedAt: number; // Unix timestamp
|
|
355
|
+
accessType: 'full' | 'session-invite';
|
|
356
|
+
sessionId?: string; // For session-invite only
|
|
357
|
+
expiresAt?: number; // Optional expiration
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Global Access Lists
|
|
362
|
+
|
|
363
|
+
When using a relay with an account, access can be managed at the account level:
|
|
364
|
+
|
|
365
|
+
```
|
|
366
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
367
|
+
│ GLOBAL ACCESS │
|
|
368
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
369
|
+
│ │
|
|
370
|
+
│ Account: brad@example.com │
|
|
371
|
+
│ ├── Machine: brad-macbook (online) │
|
|
372
|
+
│ ├── Machine: brad-server (offline) │
|
|
373
|
+
│ └── Global Access List: │
|
|
374
|
+
│ ├── Alice's Phone (full access to all machines) │
|
|
375
|
+
│ └── Bob's Laptop (full access to brad-macbook only) │
|
|
376
|
+
│ │
|
|
377
|
+
│ When global access is updated: │
|
|
378
|
+
│ 1. Relay receives add_global_access message │
|
|
379
|
+
│ 2. Relay broadcasts access_update to all connected machines │
|
|
380
|
+
│ 3. Machines update their local access list │
|
|
381
|
+
│ 4. New connections immediately respect updated access │
|
|
382
|
+
│ │
|
|
383
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Connection Flow
|
|
389
|
+
|
|
390
|
+
### Machine Connection
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
394
|
+
│ MACHINE → RELAY CONNECTION │
|
|
395
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
396
|
+
│ │
|
|
397
|
+
│ 1. Connect to relay: wss://relay/ws?role=machine │
|
|
398
|
+
│ │
|
|
399
|
+
│ 2. Relay sends relay_identity with challenge nonce │
|
|
400
|
+
│ │
|
|
401
|
+
│ 3. Machine signs nonce with Ed25519 private key │
|
|
402
|
+
│ │
|
|
403
|
+
│ 4. Machine sends register_machine with challengeResponse + keys │
|
|
404
|
+
│ │
|
|
405
|
+
│ 5. Relay verifies signature and signing key authorization │
|
|
406
|
+
│ │
|
|
407
|
+
│ 6. Relay sends: { type: "registered", machineId } │
|
|
408
|
+
│ │
|
|
409
|
+
│ 7. Relay sends access_list with all authorized clients │
|
|
410
|
+
│ │
|
|
411
|
+
│ 8. Machine is now online and accepting connections │
|
|
412
|
+
│ │
|
|
413
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Client Connection
|
|
417
|
+
|
|
418
|
+
```
|
|
419
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
420
|
+
│ CLIENT → MACHINE CONNECTION │
|
|
421
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
422
|
+
│ │
|
|
423
|
+
│ 1. Client connects: wss://relay/ws?role=client │
|
|
424
|
+
│ │
|
|
425
|
+
│ 2. Client sends: { type: "list_machines", clientIdentityId, signature } │
|
|
426
|
+
│ │
|
|
427
|
+
│ 3. Relay returns machine_list with authorized machines │
|
|
428
|
+
│ │
|
|
429
|
+
│ 4. Client sends: { type: "connect_to_machine", machineId, signature } │
|
|
430
|
+
│ │
|
|
431
|
+
│ 5. Relay sends machine: { type: "client_connected", connectionId, ... } │
|
|
432
|
+
│ │
|
|
433
|
+
│ 6. Relay sends client: { type: "connection_established", connectionId } │
|
|
434
|
+
│ │
|
|
435
|
+
│ 7. Client and machine perform X3DH handshake (routed through relay) │
|
|
436
|
+
│ │
|
|
437
|
+
│ 8. On success: encrypted terminal session established │
|
|
438
|
+
│ │
|
|
439
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Daemon Management
|
|
445
|
+
|
|
446
|
+
The `gssh serve` command can run as a daemon with status monitoring:
|
|
447
|
+
|
|
448
|
+
### Status Socket Protocol
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
// Status query via Unix socket at ~/gitspace/.serve/serve.sock
|
|
452
|
+
interface StatusResponse {
|
|
453
|
+
type: 'status';
|
|
454
|
+
version: string;
|
|
455
|
+
pid: number;
|
|
456
|
+
uptime: number; // seconds
|
|
457
|
+
relay: {
|
|
458
|
+
url: string;
|
|
459
|
+
status: 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
|
|
460
|
+
};
|
|
461
|
+
clients: number; // Connected client count
|
|
462
|
+
hosting?: {
|
|
463
|
+
subdomain: string; // e.g., "brad.gitspace.sh"
|
|
464
|
+
tunnelActive: boolean;
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Commands
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Start daemon (foreground)
|
|
473
|
+
gssh serve --relay wss://relay.example.com
|
|
474
|
+
|
|
475
|
+
# Start daemon (background)
|
|
476
|
+
gssh serve start --relay wss://relay.example.com
|
|
477
|
+
|
|
478
|
+
# Stop daemon
|
|
479
|
+
gssh serve stop
|
|
480
|
+
|
|
481
|
+
# Show status
|
|
482
|
+
gssh status
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Deployment Options
|
|
488
|
+
|
|
489
|
+
### Managed Service (gitspace.sh)
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
# Authenticate with GitHub
|
|
493
|
+
gssh auth login
|
|
494
|
+
|
|
495
|
+
# Reserve a subdomain
|
|
496
|
+
gssh host reserve myname
|
|
497
|
+
|
|
498
|
+
# Start serving (connects to gitspace.sh relay + Cloudflare tunnel)
|
|
499
|
+
gssh serve start
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Self-Hosted Relay
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
# Start relay server
|
|
506
|
+
gssh relay start --port 8080
|
|
507
|
+
|
|
508
|
+
# Authorize machine
|
|
509
|
+
gssh relay authorize gssh-pub:SIGNING_KEY:KEYEXCHANGE_KEY --label "My Machine"
|
|
510
|
+
|
|
511
|
+
# Connect machine
|
|
512
|
+
gssh serve --relay ws://localhost:8080/ws
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Security Considerations
|
|
518
|
+
|
|
519
|
+
### Threat Model
|
|
520
|
+
|
|
521
|
+
| Threat | Mitigation |
|
|
522
|
+
|--------|------------|
|
|
523
|
+
| Relay compromise | E2E encryption - relay sees only encrypted bytes |
|
|
524
|
+
| Network interception | TLS + E2E encryption with forward secrecy |
|
|
525
|
+
| Stolen client key | Revoke access: `gssh access remove <key>` |
|
|
526
|
+
| Machine key compromise | Regenerate identity: `gssh identity init --force` |
|
|
527
|
+
| Replay attacks | Per-frame random nonces, session keys |
|
|
528
|
+
| Man-in-the-middle | X3DH with identity verification |
|
|
529
|
+
|
|
530
|
+
### Key Storage
|
|
531
|
+
|
|
532
|
+
| Location | What's Stored | How |
|
|
533
|
+
|----------|---------------|-----|
|
|
534
|
+
| Machine | Identity keypair | Encrypted file + system keychain |
|
|
535
|
+
| Web client | Session keys only | Memory (cleared on close) |
|
|
536
|
+
| Relay | Public keys only | Database |
|
|
537
|
+
|
|
538
|
+
### Best Practices
|
|
539
|
+
|
|
540
|
+
1. **Protect your identity directory** - Located at `~/gitspace/.identity/`
|
|
541
|
+
2. **Use labeled access entries** - Know who has access
|
|
542
|
+
3. **Audit access regularly** - Run `gssh access list`
|
|
543
|
+
4. **Revoke unused access** - Remove former collaborators
|
|
544
|
+
5. **Use session invites for demos** - Time-limited, read-only
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
**Last Updated:** 2025-01
|
|
549
|
+
**Status:** Implemented
|