Skip to content

add encrypted Nostr session sharing - #8922

Merged
michaelneale merged 9 commits into
aaif-goose:mainfrom
callebtc:nostr-export
May 13, 2026
Merged

add encrypted Nostr session sharing#8922
michaelneale merged 9 commits into
aaif-goose:mainfrom
callebtc:nostr-export

Conversation

@callebtc

@callebtc callebtc commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Overview

Category: new-feature
User Impact: Users can share and import Goose conversations through encrypted Nostr links instead of manually exchanging exported JSON files.
Problem: Session export already supports JSON files but leaves users to manage transport themselves. There was also no direct way for a receiving Goose client to fetch and restore a shared conversation from a link.
Solution: Add an isolated Nostr sharing layer that encrypts the session JSON with NIP-44 v2, publishes it as kind 30278 to configurable relays, and encodes the event reference plus decryption key in a Goose deep link. The CLI, server API, and desktop session UI now reuse that path for exporting, importing, and handling Nostr session deep links.

Changes

File changes

Cargo.lock
Adds the Nostr SDK, NIP-44 crypto, relay-pool, and supporting dependency graph needed to publish and fetch encrypted Nostr events.

crates/goose/Cargo.toml
Adds nostr, nostr-sdk, and an optional rustls dependency wired into the existing rustls-tls feature so the live Nostr client can install a Rustls crypto provider.

crates/goose/src/session/mod.rs
Exports the new isolated nostr_share module from the session domain.

crates/goose/src/session/nostr_share.rs
Implements the reusable core Nostr share/import logic: default/configured relays, NIP-44 v2 encryption/decryption, kind 30278 event creation, nevent encoding, Goose deep-link creation, live publish/fetch clients, and unit tests.

crates/goose-cli/src/cli.rs
Adds CLI flags for goose session export --format json --nostr --relay ... and a new goose session import command that accepts either JSON files or Nostr share links.

crates/goose-cli/src/commands/session.rs
Wires CLI export/import to the shared Nostr module while preserving the existing JSON/YAML/Markdown export behavior.

crates/goose-server/src/routes/session.rs
Adds desktop-facing endpoints for sharing a session to Nostr and importing a Nostr share link back through the normal session import path.

crates/goose-server/src/openapi.rs
Registers the new Nostr session endpoints and schemas in the OpenAPI spec.

ui/desktop/openapi.json
Regenerates the desktop OpenAPI schema with the new Nostr share/import endpoints.

ui/desktop/src/api/index.ts
Regenerates the desktop API exports so UI code can call the new Nostr endpoints.

ui/desktop/src/api/sdk.gen.ts
Regenerates typed API client functions for importSessionNostr and shareSessionNostr.

ui/desktop/src/api/types.gen.ts
Regenerates request/response types for Nostr session import and share responses.

ui/desktop/src/components/sessions/SessionListView.tsx
Adds desktop UI controls to create encrypted Nostr share links from session cards and import a pasted Goose Nostr share link from Chat History.

ui/desktop/src/sessionLinks.ts
Routes goose://sessions/nostr?... deep links through the Nostr import endpoint before returning users to the session list.

Reproduction Steps

  1. Start Goose Desktop and create or use an existing chat session.
  2. Open Chat History and hover a session card.
  3. Click the Nostr share icon to publish the encrypted session event and show a copyable goose://sessions/nostr?... link.
  4. Use Chat History's Import Link button and paste that link.
  5. Confirm the session is fetched from the relays, decrypted, imported, and appears in the session list.
  6. From the CLI, run goose session export --session-id <id> --format json --nostr and verify it prints a Goose Nostr share link.
  7. From another Goose client, run goose session import '<goose://sessions/nostr?...>' and verify it imports the session.

Validation

  • cargo fmt
  • cargo test -p goose nostr_share --lib
  • cargo test -p goose-cli parses_session_export_nostr_relays --lib
  • cargo test -p goose-cli parses_session_import_nostr_link --lib
  • cargo check -p goose-server
  • pnpm run generate-api
  • pnpm run typecheck

@callebtc

Copy link
Copy Markdown
Contributor Author

CLI:
image

image

GUI:
image

image

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c4c3eaa8c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/sessionLinks.ts Outdated
Signed-off-by: callebtc <93376500+callebtc@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7ef2cf3a6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/sessionLinks.ts Outdated
Comment on lines +24 to +27
await importSessionNostr({
body: { deeplink: url },
throwOnError: true,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle Nostr deep-link errors in the sessions flow

importSessionNostr is called inside the shared try block, so any Nostr import failure (invalid link, relay fetch/decrypt error, server-side 400/500) falls through to the generic catch path that always navigates to the shared-session error view. That view is specific to share-token links and shows an irrelevant shareToken for goose://sessions/nostr URLs, which breaks the intended import flow for failed Nostr links. Split Nostr handling/error routing from legacy shared-session handling so Nostr failures stay in the sessions import UX.

Useful? React with 👍 / 👎.

Douwe Osinga added 2 commits May 12, 2026 14:58
- Merge origin/main and resolve conflicts
- Fix Nostr deep link routing: separate from shared-session flow so
  Nostr imports navigate to /sessions and errors show a toast instead
  of the irrelevant shared-session error view (addresses Codex P1/P2)
- Remove dead 'rustls' feature flag (rustls-tls already covers it)
- Change ShareSessionNostrRequest.relays from Option<Vec> to Vec with
  serde default (avoid unnecessary Option)
- Fix clipboard copy error using wrong i18n message (importFailed)
- Remove trivial CLI arg-parsing tests (just testing clap)
- Regenerate openapi.json

Signed-off-by: Douwe Osinga <douwe@squareup.com>
@DOsinga DOsinga added the needs_human label to set when a robot looks at a PR and can't handle it label May 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b167989639

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/desktop/src/App.tsx
Comment on lines +432 to +434
await importNostrSessionFromDeepLink(link);
navigate('/sessions');
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reload session list after Nostr deep-link import

After a successful goose://sessions/nostr import this path only calls navigate('/sessions'), but SessionListView fetches sessions only on mount (useEffect(() => { loadSessions(); }, [loadSessions])). If the user is already on /sessions when the deep link arrives, the list component stays mounted and no refresh runs, so the newly imported conversation does not appear until a manual reload or later action. Trigger a sessions refresh (or navigate with a state signal that forces reload) after import success.

Useful? React with 👍 / 👎.

@DOsinga DOsinga left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving this PR. The code is well-structured — clean trait abstractions for testability, proper NIP-44 v2 encryption with ephemeral keys, and good round-trip tests.

I've pushed a fix commit that:

  • Merges origin/main and resolves conflicts
  • Fixes the Nostr deep link routing (Codex P1/P2): Nostr imports now navigate to /sessions with errors shown as toasts, instead of falling through to the shared-session error view
  • Removes the dead standalone rustls feature flag (rustls-tls already covers it)
  • Changes ShareSessionNostrRequest.relays from Option<Vec<String>> to Vec<String> with #[serde(default)]
  • Fixes clipboard copy error using wrong i18n message
  • Removes trivial CLI arg-parsing tests (just testing clap)
  • Regenerates openapi.json

DCO: @callebtc your commits are missing Signed-off-by lines which will fail the DCO check. Please amend your commits with git commit --amend -s or rebase with git rebase --signoff.

@michaelneale

Copy link
Copy Markdown
Collaborator

oh this is really exciting @callebtc ! (it is like you were listening to @DOsinga and myself chatting in a room a few weeks back!) - taking a look, I love this idea.

Signed-off-by: Michael Neale <michael.neale@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d07b40892

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/App.tsx
Comment on lines +431 to +433
if (link.startsWith('goose://sessions/nostr')) {
await importNostrSessionFromDeepLink(link);
navigate('/sessions');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact Nostr deep-link secrets from logs

When this Nostr branch handles an OS deep link, it has already logged the full link a few lines above; these new Nostr URLs include the key query parameter that is sufficient to decrypt the shared session. Anyone with access to desktop logs or diagnostics could recover the encrypted conversation, so redact the key value or log only the link type before handling Nostr imports.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little over dramatic 🙄 - a log on the disk for a session which is in sessions.db in sql lite.. on the same disk (!) so ... that is silly

@michaelneale

Copy link
Copy Markdown
Collaborator

I am updating this, got one more change to go and then will merge it in... thanks.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 363962180b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread ui/desktop/src/App.tsx
setSharedSessionError(null);
try {
if (link.startsWith('goose://sessions/nostr')) {
await importNostrSessionFromDeepLink(link);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard Nostr imports against duplicate deep-link delivery

In the Windows/Linux launch path I checked ui/desktop/src/main.ts: processProtocolUrl sends open-shared-session for sessions links without clearing pendingDeepLink, and the later react-ready handler sends the same pending link again. With this new branch, each delivery calls the Nostr import endpoint and SessionManager::import_session creates a fresh session, so a single OS goose://sessions/nostr?... link can produce duplicate imported conversations. Make the Nostr import path idempotent or dedupe/clear the pending link before importing.

Useful? React with 👍 / 👎.

@michaelneale
michaelneale added this pull request to the merge queue May 13, 2026
Merged via the queue into aaif-goose:main with commit dbbee1c May 13, 2026
22 checks passed
shafqatevo pushed a commit to shafqatevo/goose that referenced this pull request Aug 7, 2026
Signed-off-by: callebtc <93376500+callebtc@users.noreply.github.com>
Signed-off-by: Douwe Osinga <douwe@squareup.com>
Signed-off-by: Michael Neale <michael.neale@gmail.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs_human label to set when a robot looks at a PR and can't handle it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants