add encrypted Nostr session sharing - #8922
Conversation
There was a problem hiding this comment.
💡 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".
Signed-off-by: callebtc <93376500+callebtc@users.noreply.github.com>
There was a problem hiding this comment.
💡 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".
| await importSessionNostr({ | ||
| body: { deeplink: url }, | ||
| throwOnError: true, | ||
| }); |
There was a problem hiding this comment.
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 👍 / 👎.
# Conflicts: # Cargo.lock
- 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>
There was a problem hiding this comment.
💡 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".
| await importNostrSessionFromDeepLink(link); | ||
| navigate('/sessions'); | ||
| return; |
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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
/sessionswith errors shown as toasts, instead of falling through to the shared-session error view - Removes the dead standalone
rustlsfeature flag (rustls-tlsalready covers it) - Changes
ShareSessionNostrRequest.relaysfromOption<Vec<String>>toVec<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.
Signed-off-by: Michael Neale <michael.neale@gmail.com>
There was a problem hiding this comment.
💡 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".
| if (link.startsWith('goose://sessions/nostr')) { | ||
| await importNostrSessionFromDeepLink(link); | ||
| navigate('/sessions'); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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
|
I am updating this, got one more change to go and then will merge it in... thanks. |
There was a problem hiding this comment.
💡 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".
| setSharedSessionError(null); | ||
| try { | ||
| if (link.startsWith('goose://sessions/nostr')) { | ||
| await importNostrSessionFromDeepLink(link); |
There was a problem hiding this comment.
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 👍 / 👎.
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>




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 optionalrustlsdependency wired into the existingrustls-tlsfeature so the live Nostr client can install a Rustls crypto provider.crates/goose/src/session/mod.rs
Exports the new isolated
nostr_sharemodule 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,
neventencoding, 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 newgoose session importcommand 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
importSessionNostrandshareSessionNostr.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
goose://sessions/nostr?...link.goose session export --session-id <id> --format json --nostrand verify it prints a Goose Nostr share link.goose session import '<goose://sessions/nostr?...>'and verify it imports the session.Validation
cargo fmtcargo test -p goose nostr_share --libcargo test -p goose-cli parses_session_export_nostr_relays --libcargo test -p goose-cli parses_session_import_nostr_link --libcargo check -p goose-serverpnpm run generate-apipnpm run typecheck