📘1. 概要
PAssist は Windows 向けのリモート操作ツールです。共有するのは選んだ 1 つのウィンドウだけ。操作する相手はブラウザを開くだけ(インストール・アカウント不要)。映像と操作は WebRTC で暗号化されて流れ、デスクトップ全体は構造的に映りません。
✨2. できること
| 要件 | 実装 |
|---|---|
| 任意のウィンドウを1つだけ選ぶ | 開いているウィンドウ一覧から選択。キャプチャ元は window のみ列挙し screen は一切扱わない。 |
| URL/QRを発行 | 選択すると共有URL(…/s/<token>)を自動発行。スマホ用にQRコードも表示。 |
| ブラウザだけで一時操作 | WebRTC で映像、DataChannel でマウス/キー操作。相手はブラウザのみ。 |
| 複数人で同時に | 同時接続を設定可能。操作できるのは常に1人(最初の接続者)、ほかは閲覧。全員「閲覧のみ」にも切替可。 |
| デスクトップ全体は出さない | 選んだ1ウィンドウの映像のみ。構造的に画面全体を渡せない設計。 |
| アカウント不要 | URLのトークンだけで接続。ログインなし。 |
| 一時利用 | セッションに有効期限(既定30分、変更可・無期限も可)。期限切れで自動失効。 |
| 検証できる | サーバが「公開コードと同一」であることをブラウザが自動で暗号学的に検証(詳細)。 |
🧩3. アーキテクチャ
┌──────────────────────── ホストPC (PAssist) ────────────────────────┐
│ Electron アプリ(タスクトレイ常駐) │
│ ├─ ウィンドウ選択 / 管理UI(renderer) │
│ ├─ シグナリング/配信サーバ(Node: express + ws)※内部起動 │
│ └─ 入力注入(nut.js:座標クランプ+危険キー遮断) │
└───────────┬───────────────────────────────────────┬───────────────┘
│ WebRTC 映像(DTLS-SRTP) │ シグナリング(WSS)
│ + DataChannel 操作 │ + ページ配信
▼ ▼
┌──────────────┐ (UPnP / Cloudflare トンネル)
│ 操作する人 │ ブラウザでURLを開くだけ
│ ブラウザのみ │ iframe 埋め込みも可
└──────────────┘
- ホスト(
host/・Electron):ウィンドウのキャプチャ(getDisplayMedia+setDisplayMediaRequestHandler)、WebRTC の offer、入力注入。シグナリングサーバを内部で起動。 - サーバ(
server/・Node express + ws):セッション/トークン管理、SDP/ICE 中継、ビューア配信、TURN クレデンシャル発行。ホスト内蔵のほか、独立サーバとしても運用可(Docker image を配布)。 - ビューア(
server/public/viewer.html):ブラウザの素のページ(WebRTC の answer 側)。映像表示・入力送信・サーバ検証。 - 暗号化:映像・操作は WebRTC(DTLS-SRTP)で常に暗号化。シグナリング/ページは https/wss 化可能。
🚀4. 使い方
ホスト(共有する人)
PAssist.exeをダブルクリック(開発時はnpm start)- 共有したいウィンドウをクリックして選択
- 表示された共有URLをコピー、またはQRコードを相手に見せる
- 接続リクエストが来たら「許可」(「この端末を信頼」で次回から自動承認)
ゲスト(操作する人)
- 送られたURLをブラウザで開く(スマホはQRを読み取るだけ)
- 「クリックして操作を開始」→ マウス/キーボードで対象ウィンドウを操作。終了はブラウザを閉じるだけ。
タスクトレイ常駐
- ウィンドウの「×」は終了せずトレイに格納。トレイアイコンのクリックで再表示。
- トレイ右クリック →「管理画面を開く」「ログイン時に自動起動」「終了」。
🔑5. アクセス制御と信頼
「誰が繋げるか」をセッションごと(または既定値 ACCESS_MODE)で選べます。
| モード | 挙動 | 強さ |
|---|---|---|
approve(既定) | ホストが「許可」するまで接続不可。映像も流れない。 | 最も安全 |
pin | セッションごとに発行される6桁ワンタイムPINが一致すれば接続(ホスト承認なし)。 | 中 |
invite | 招待リンク(信頼クレデンシャル付きURL)を持つ相手のみ。リンクが無いと接続不可。 | 中〜高 |
token | URLのトークンだけで即接続(承認なし)。手軽だが最も緩い。 | 緩い |
信頼済み端末(事前承認)
初回に「この端末を信頼」、または「招待リンク」を渡すと、その端末は次回から自動承認されます。仕組みは端末ごとの clientId + secret。ホストは secret のハッシュ(SHA-256)だけを保存し(userData/passist-trust.json)、平文はビューアの localStorage のみに置かれます。招待リンクは …/s/<token>#k=<clientId>.<secret> の形。
同時接続・閲覧のみ・有効期限
- 同時接続:人数を設定可能。操作できるのは常に1人(最初の接続者)、ほかは自動的に閲覧のみ。
- 閲覧のみ:全員を操作不可(画面共有だけ)に切替可能。
- 有効期限:既定30分。30分/1時間/3時間/6時間/無期限から選択。期限切れで全接続を自動切断。
🌍6. インターネット公開
方法A:UPnP 自動公開(公開IP+UPnP有効が前提)
起動時にアプリが Webポート(TCP 8443)を UPnP で自動開放し、共有URLを http://<公開IP>:8443/s/… に自動設定します。
npm start # ログに「[host] インターネット公開: http://<公開IP>:8443 …」が出れば成功 # 同一LAN内のみにしたい場合: $env:PASSIST_PUBLIC="0"; npm start
方法B:Cloudflare トンネル(https・ポート開け不要・推奨)
独自ドメイン(例 passist.example.com)で正規の https URL になります。Cloudflare 側の公開ホスト名は タイプ=HTTP / URL=localhost:8443 / パス=空。
$env:PASSIST_PUBLIC="0"; $env:PUBLIC_BASE_URL="https://passist.example.com"; npm start # 共有URL = https://passist.example.com/s/…(ブラウザで開くだけ)
🛡️7. セキュリティ
| 観点 | 対策 |
|---|---|
| 1ウィンドウ限定 | キャプチャ元に screen を列挙せず、選択IDに固定。デスクトップ・他ウィンドウは映らない。 |
| 一時利用 | セッションに有効期限(既定30分)。期限切れで自動失効。 |
| 勝手に繋がせない | 既定でホスト承認制。許可するまで映像は流れない。PIN/招待リンクのモードも有り。 |
| 事前承認(信頼端末) | 端末ごとのクレデンシャル。ホストはハッシュ(SHA-256)のみ保存。 |
| 操作範囲の限定 | 入力座標を対象ウィンドウの矩形にクランプ。窓外には出さない。 |
| デスクトップへの脱出防止 | Win / Alt+Tab / Alt+F4 / Ctrl+Esc / タスクマネージャ 等のキーをブロック。 |
| 暗号化 | 映像・操作は常に DTLS-SRTP(AES-128-GCM / ECDHE)。鍵はホストと相手だけが持ち、サーバ・TURN は中身を見られない。 |
| サーバの真正性 | 稼働中サーバが公開コードと同一であることを暗号学的に検証可能(次節)。 |
🔐8. 検証・署名
signaling サーバの Docker image は、GitHub Actions で Reproducible Build(再現可能ビルド)され、Cosign keyless 署名(GitHub OIDC → Sigstore Fulcio の短期証明書 → Rekor 透明性ログ)と SLSA provenance + SBOM が付きます。秘密鍵は運営者が保持しません。
ビューア/ホストの「PAssist について」を開くと、ブラウザ自身がその場で Rekor の Merkle 証明・署名・イメージ digest・証明書の発行元を検証し、✓ を表示します(専門知識・追加インストール不要)。CLI でフル検証する場合:
cosign verify ghcr.io/paps-jp/passist-signaling@<digest> \
--certificate-identity-regexp 'https://github.com/paps-jp/passist' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
⚙️9. 設定(環境変数)
ホスト側
| 変数 | 既定 | 説明 |
|---|---|---|
PASSIST_PUBLIC | 有効 | 0 で UPnP 公開を無効(同一LAN内のみ) |
PUBLIC_BASE_URL | — | 共有URLのベース(トンネル使用時に指定) |
REMOTE_SERVER_URL | — | 外部シグナリングを使う場合の接続先(内部起動を無効化) |
サーバ側
| 変数 | 既定 | 説明 |
|---|---|---|
PORT | 8443 | サーバの待受ポート |
ACCESS_MODE | approve | 既定アクセスモード:approve / pin / invite / token |
SESSION_TTL_MS | 1800000 | セッション有効期限(ms)。0 で無期限 |
TLS_CERT / TLS_KEY | — | 指定すると https/wss で待受 |
ICE_STUN_URL | Google STUN | 配布する STUN サーバ |
ICE_TURN_URL / ICE_TURN_USER / ICE_TURN_PASS | — | 静的(固定認証)TURN |
SIGN_TURN_URLS / TURN_AUTH_SECRET / SIGN_TURN_USERNAME / SIGN_TURN_TTL_SEC | — | 短期認証TURN(HMAC で期限付きクレデンシャルを発行・自動失効) |
RELAY_BUDGET_BPS / RELAY_MIN_BPS / RELAY_MAX_BPS | — | TURN 中継時の送信ビットレート上限(コスト管理) |
GIT_COMMIT / IMAGE_DIGEST / BUILD_TIMESTAMP / GIT_TAG はビルド時に自動で注入されるビルド情報で、/api/build から参照できます(手動設定不要)。
🐳10. セルフホスト・運用
signaling サーバは独立運用できます。deploy/docker-compose.yml が Caddy(自動TLS)+ signaling(GHCR から pull)+ coturn(TURN・同居)を立ち上げます。
# VPS 初期化(UFW・Docker 導入・.env 雛形生成)
bash deploy/bootstrap.sh
# .env に最低限を設定
# DOMAIN=passist.example.com
# ICE_TURN_* / TURN 認証 など
docker compose -f deploy/docker-compose.yml up -d
# 更新: docker compose pull && docker compose up -d
- image の真正性は pull 前後に
cosign verifyで確認できます(→ 検証・署名)。 - TURN だけ別ホストに分離したい場合は
deploy/coturn-only/を使用。 - 統計:稼働サーバは
/api/stats(CORS 許可)で匿名の利用統計を公開。ダッシュボードは stats.html。
🔨11. ビルドと配布
ホスト(PAssist.exe)
npm install # host/server の依存を一括導入 npm start # 開発起動(Electron) npm test # 信頼ロジックの単体テスト npm --prefix host run dist # 配布用 PAssist.exe(portable)を生成
- 成果物:
dist\PAssist.exe(単一実行ファイル・portable)。サーバ一式はresources\serverに同梱、ネイティブ(nut.js)は asar から展開。 - 入力注入が「閲覧のみ」になる場合は
npm run rebuild(nut.js を Electron 用に再ビルド)。
signaling サーバ(自動・署名付き)
v* タグを push すると release-signaling.yml が起動し、Reproducible Build → GHCR へ push → SLSA provenance+SBOM 添付 → Cosign keyless 署名(Rekor 記録)まで自動実行します。
git tag vX.Y.Z && git push origin vX.Y.Z # あとは CI が全部やる
🖼️12. iframe 埋め込み
ビューアは普通の Web ページなので iframe に埋め込めます。
<iframe src="https://passist.example.com/s/<token>" allow="fullscreen; autoplay" style="width:100%;height:600px;border:0" title="PAssist"></iframe>
- 親ページは https 推奨。
allow="fullscreen"は全画面ボタン用。 - 埋め込み元を限定するなら CSP
frame-ancestorsを返す。 sandboxを付けるならallow-scripts allow-same-origin allow-fullscreenが必要。
📌13. 制限と今後
現在の制限
- ホストは Windows のみ(nut.js / Windows Graphics Capture)。
- 厳しい NAT の相手は TURN が必要な場合あり。
- exe は未署名(SmartScreen 警告)。
- 操作は OS レベルの実入力(ホストの実カーソルが動く)。
今後の候補
- exe の SHA256SUMS を cosign sign-blob で署名し release に添付。
- コード署名(EV / Azure Trusted Signing 等)で SmartScreen 通過。
- 送信ビットレートの自動調整・自動更新。
📄14. ライセンス
PAssist は MIT License で公開しています。OSI 認定の最もシンプルな許諾型 OSS ライセンスで、商用利用を含むほぼすべての利用が自由です。