diff --git a/packages/app/e2e/support/helpers/pair-device.ts b/packages/app/e2e/support/helpers/pair-device.ts index a97a5f51e9..d8908015b8 100644 --- a/packages/app/e2e/support/helpers/pair-device.ts +++ b/packages/app/e2e/support/helpers/pair-device.ts @@ -113,6 +113,11 @@ export async function enableRelayAndExpectOffer(page: Page): Promise { export async function expectPairingOffer(page: Page): Promise { await expect(page.getByRole("img", { name: "Pairing QR code" })).toBeVisible(); await expect(page.getByRole("textbox", { name: "Pairing link" })).toHaveValue(/#offer=/); + await expect( + page.getByRole("alert").filter({ + hasText: "Treat this pairing link like a password. Anyone with it can access this daemon.", + }), + ).toBeVisible(); await expect(page.getByRole("button", { name: "Copy", exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Enable relay", exact: true })).toHaveCount(0); } diff --git a/packages/app/src/desktop/components/pair-device-section.tsx b/packages/app/src/desktop/components/pair-device-section.tsx index eff6a45762..a036870d56 100644 --- a/packages/app/src/desktop/components/pair-device-section.tsx +++ b/packages/app/src/desktop/components/pair-device-section.tsx @@ -262,6 +262,7 @@ function PairingOffer(props: PairDeviceBodyProps & { offer: { url: string } }) { {props.copied ? t("pairing.device.copied") : t("pairing.device.copy")} + ); } diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index b27b45489e..0a2accbf57 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -1630,6 +1630,8 @@ export const ar: TranslationResources = { updateRequired: "حدّث المضيف لتمكين التتابع من Paseo Desktop.", unavailable: "عرض الاقتران غير متاح.", hint: "قم بمسح رمز QR هذا باستخدام Paseo على هاتفك، أو انسخ الرابط أدناه.", + securityWarning: + "تعامل مع رابط الاقتران هذا ككلمة مرور. يمكن لأي شخص يملكه الوصول إلى هذا البرنامج الخفي.", qrUnavailable: "رمز QR غير متاح.", qrAccessibility: "رمز QR للإقران", retry: "أعد المحاولة", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index d874b9db8c..24018981ab 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -1640,6 +1640,8 @@ export const en = { updateRequired: "Update the host to enable relay from Paseo Desktop.", unavailable: "Pairing offer unavailable.", hint: "Scan this QR code with Paseo on your phone, or copy the link below.", + securityWarning: + "Treat this pairing link like a password. Anyone with it can access this daemon.", qrUnavailable: "QR code unavailable.", qrAccessibility: "Pairing QR code", retry: "Retry", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 66499703ae..e86ed2b1ab 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -1676,6 +1676,8 @@ export const es: TranslationResources = { updateRequired: "Actualice el host para habilitar el relé desde Paseo Desktop.", unavailable: "Oferta de maridaje no disponible.", hint: "Escanee este códigoQRconPaseoen su teléfono o copie el enlace a continuación.", + securityWarning: + "Trata este enlace de emparejamiento como una contraseña. Cualquiera que lo tenga puede acceder a este daemon.", qrUnavailable: "CódigoQRno disponible.", qrAccessibility: "Código QR de emparejamiento", retry: "Rever", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index 0be7692795..f68dd38198 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -1680,6 +1680,8 @@ export const fr: TranslationResources = { updateRequired: "Mettez à jour l’hôte pour activer le relais depuis Paseo Desktop.", unavailable: "Offre de jumelage indisponible.", hint: "Scannez ce codeQRavecPaseosur votre téléphone ou copiez le lien ci-dessous.", + securityWarning: + "Traitez ce lien d’association comme un mot de passe. Toute personne qui le possède peut accéder à ce daemon.", qrUnavailable: "CodeQRindisponible.", qrAccessibility: "Code QR de couplage", retry: "Réessayer", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index 3e699429f9..c65aa6ada0 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -1647,6 +1647,8 @@ export const ja: TranslationResources = { updateRequired: "Paseo Desktop からリレーを有効にするにはホストを更新してください。", unavailable: "ペアリングオファーが利用できません。", hint: "スマートフォンのPaseoでこのQRコードをスキャンするか、以下のリンクをコピーしてください。", + securityWarning: + "このペアリングリンクはパスワードと同様に扱ってください。リンクを知っている人は誰でもこのデーモンにアクセスできます。", qrUnavailable: "QRコードが利用できません。", qrAccessibility: "ペアリング QR コード", retry: "再試行", diff --git a/packages/app/src/i18n/resources/ko.ts b/packages/app/src/i18n/resources/ko.ts index 483c3d03ad..de784dabb4 100644 --- a/packages/app/src/i18n/resources/ko.ts +++ b/packages/app/src/i18n/resources/ko.ts @@ -1640,6 +1640,8 @@ export const ko: TranslationResources = { updateRequired: "Paseo 데스크톱에서 릴레이를 활성화하려면 호스트를 업데이트하세요.", unavailable: "페어링 정보를 사용할 수 없습니다.", hint: "휴대폰의 Paseo로 이 QR 코드를 스캔하거나 아래 링크를 복사하세요.", + securityWarning: + "이 페어링 링크는 비밀번호처럼 취급하세요. 링크를 가진 사람은 누구나 이 데몬에 접근할 수 있습니다.", qrUnavailable: "QR 코드를 사용할 수 없습니다.", qrAccessibility: "페어링 QR 코드", retry: "다시 시도", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index 1ca1661745..400ccb9453 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -1662,6 +1662,8 @@ export const ptBR: TranslationResources = { updateRequired: "Atualize o host para ativar o relay pelo Paseo Desktop.", unavailable: "Oferta de pareamento indisponível.", hint: "Escaneie este QR code com o Paseo no seu celular ou copie o link abaixo.", + securityWarning: + "Trate este link de pareamento como uma senha. Qualquer pessoa com o link pode acessar este daemon.", qrUnavailable: "QR code indisponível.", qrAccessibility: "Código QR de pareamento", retry: "Tentar novamente", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index b383b59f45..ac8059b81a 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -1660,6 +1660,8 @@ export const ru: TranslationResources = { updateRequired: "Обновите хост, чтобы включить ретранслятор из Paseo Desktop.", unavailable: "Данные для сопряжения недоступны.", hint: "Отсканируйте этот QR-код с помощью Paseo на телефоне или скопируйте ссылку ниже.", + securityWarning: + "Обращайтесь с этой ссылкой для сопряжения как с паролем. Любой, у кого она есть, может получить доступ к этому демону.", qrUnavailable: "QR-код недоступен.", qrAccessibility: "QR-код сопряжения", retry: "Повторить попытку", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index be7b816b04..9c456b9e43 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -1610,6 +1610,7 @@ export const zhCN: TranslationResources = { updateRequired: "请更新主机,以便从 Paseo Desktop 启用中继。", unavailable: "配对 offer 不可用。", hint: "用手机上的 Paseo 扫描此二维码,或复制下方链接。", + securityWarning: "请像保管密码一样保管此配对链接。任何获得此链接的人都可以访问此守护进程。", qrUnavailable: "二维码不可用。", qrAccessibility: "配对二维码", retry: "重试", diff --git a/packages/cli/src/output/pairing.test.ts b/packages/cli/src/output/pairing.test.ts index 00ed6b0a85..89fc31d85e 100644 --- a/packages/cli/src/output/pairing.test.ts +++ b/packages/cli/src/output/pairing.test.ts @@ -10,6 +10,9 @@ describe("formatPairingInstructions", () => { expect(output).toContain(QR); expect(output.split("\n")).toContain(URL); + expect(output).toContain( + "Treat this pairing link like a password. Anyone with it can access this daemon.", + ); }); it("does not print a QR that would reach the terminal edge", () => { diff --git a/packages/cli/src/output/pairing.ts b/packages/cli/src/output/pairing.ts index 6d0da5bf6f..741c8fd056 100644 --- a/packages/cli/src/output/pairing.ts +++ b/packages/cli/src/output/pairing.ts @@ -33,5 +33,5 @@ function formatQr(qr: string | null, columns: number | undefined): string { } export function formatPairingInstructions({ url, qr, columns }: PairingInstructions): string { - return `\nScan to pair:\n${formatQr(qr, columns)}\n\nPairing link:\n${url}\n`; + return `\nScan to pair:\n${formatQr(qr, columns)}\n\nPairing link:\n${url}\n\nTreat this pairing link like a password. Anyone with it can access this daemon.\n`; } diff --git a/public-docs/security.md b/public-docs/security.md index 52363a10fa..bfe0993bdd 100644 --- a/public-docs/security.md +++ b/public-docs/security.md @@ -52,8 +52,6 @@ The daemon requires a valid cryptographic handshake before processing any comman The QR code or pairing link is the trust anchor. It contains the daemon's public key, which is required to establish the encrypted connection. Treat it like a password, don't share it publicly. -If you believe a pairing offer has been compromised, restart the daemon to generate a new session ID and rotate the relay pairing. - ## Direct connections By default, the daemon listens on `127.0.0.1:6767` (localhost only). This is safe for local CLI usage but not reachable from your phone or other devices.