โ† Dashboard ยท Docs ยทSharing

Sharing in Grove

<!-- grove:last-verified v1.98 -->

Overview

Grove sharing is peer-to-peer and cryptographic. When you share a file, you create a signed grant that gives the recipient permission to decrypt and access it. No central server involved.


How It Works

Sharing a File

1. You select a file in the Files tab and click ๐Ÿ”— Share (โ‹ฏ menu)

2. A modal shows a search box โ€” type to find friend cells (only friends can receive shares)

3. Select a recipient and click Share

4. Grove creates a ShareGrant for the recipient:

5. Grant is pushed to the recipient's cell via POST /api/receive-grant

6. Manifest and chunks are pushed so the file is immediately accessible

Receiving a Shared File

1. Grant arrives at your cell and is verified (Ed25519 signature check)

2. File appears in your Feed tab, and received shared folders also render as playable libraries in the ๐Ÿ“ฅ Shared with me Smart Folder (see below)

3. If chunks are already on your cell โ†’ file is immediately viewable

4. Click ๐ŸŒฒ Add to Grove (tap twice to confirm) to save the file: decrypts it, saves to GroveHome preserving the sender's sub-folder layout, re-ingests as your own file, and syncs to your peers. Re-ingesting content you already hold is deduped (the #174 content-identity guard returns the existing manifest instead of minting a duplicate), so Add-to-Grove / re-share won't spawn double manifests for the same plaintext.

Unsharing

1. Click โ‹ฏ โ†’ ๐Ÿšซ Unshare on a shared file (or from the Feed's outgoing section)

2. Grant is deleted from their cell via POST /api/revoke-grant

3. They lose access (though if they already clicked Add to Grove, they have their own copy)

Revoke is best-effort and immediate, but hard-gated (C1 invariant): /api/revoke-grant only deletes a grant when the request's granted_by matches the authenticated X-Grove-Sender โ€” only the original signer of a grant may revoke it. A peer holding a valid peer-secret can no longer delete someone else's grants. Revoke also purges the cached portal thumbnail so a revoked recipient can't keep pulling it.

Autonomous reclamation

Grant rows also self-heal even when a revoke never reaches the other cell โ€” the fix that matters for self-update family cells (nook/palooza/family) that never run grove gc. On a conservative ~daily background gate (_maybe_reclaim_old_versions), each cell runs sweep_orphan_grants, which reaps any grants-table row whose manifest_hash is no longer a live manifest here. This converges both sides of a dead share over time: the sender drops its own stale row once its manifest is gone, and the recipient drops the incoming row once the sender's tombstone propagates. A grant with an empty/None manifest_hash is kept (ambiguous โ€” never delete a live grant on a guess).

The same gate collapses byte-identical duplicate manifests (_collapse_phantom_manifests) โ€” but a duplicate whose content is shared (its content hash has a grant) is never collapsed, so reclamation can't break a live share.


Folder Sharing

Auto-Share Folders

When sharing a file from within a folder, you can enable ๐Ÿ“‚ Auto-share new files added to this folder:

You can also open Auto-Share Settings directly from the folder โ‹ฏ menu (๐Ÿ”— Auto-Share).

Batch Sharing

Select multiple files with checkboxes โ†’ batch Share button โ†’ share all to the same recipient.


Show in Portal

Show in Portal lets the owner make a specific file or folder accessible through their linked WAN gateway portal โ€” without copying keys off the cell.

1. Set up Remote Access first (Settings โ†’ Advanced โ†’ Remote Access)

2. In the Files tab, click โ‹ฏ on any file or folder โ†’ ๐ŸŒ Show in Portal

3. The button toggles: when active it reads ๐ŸŒ โœ“ In Portal โ€” remove

4. The file is now visible in your portal account's file view at the gateway

Alternatively, drop files into ~/GroveHome/Remote to auto-expose them.

Endpoint: POST /api/portal/show-in-portal

Media keeps its folder tree in the portal

A file's filename and path live encrypted inside its manifest (see ยง7, manifest-metadata privacy), and a portal account can't decrypt them โ€” so previously portal-shared media showed up as a flat list of "unknown" files.

Now, when you Show-in-Portal (or otherwise share to a portal account), the grant carries the file's display filename and its GroveHome-relative path (rel_path) in plaintext, per recipient. _share_to_portal_user sends them; the gateway stores them via POST /api/share-grant (new rel_path grant column); _get_portal_manifests falls back to these when the manifest's own name/path are encrypted.

The result: the portal rebuilds the real nested folder tree (e.g. Radiohead/OK Computer/โ€ฆ, The Office/Season 1/โ€ฆ), so a shared folder of music or video auto-detects its media role and plays in the portal's Files view โ€” the same library + player the owner sees on the dashboard.


Shared with me

The owner dashboard has a synthetic ๐Ÿ“ฅ Shared with me Smart Folder โ€” the owner-side parity of the portal folder tree above. Received shares are grouped by sender, and a shared folder of media auto-renders there as a browsable, playable library:

source_folder / rel_path are unsigned grant metadata (they can't ride inside the signed manifest โ€” ยง7 keeps the real path encrypted), so they are traversal-sanitized on receipt (_swm_sanitize_relpath, reduced to safe segments / a bare basename) before they reach the DB or drive any Add-to-Grove placement. This blocks a forwarding relay from rewriting the path on an otherwise validly-signed grant to escape GroveHome.


Renderers and Site Audience

A folder can be published as a web site at /site/<slug>/. This is separate from peer-to-peer sharing โ€” it serves the folder contents as a rendered web page.

Setting Up a Site

From inside a folder in the Files tab, look for the ๐Ÿ“ก serve as site link, or use โ‹ฏ โ†’ the folder actions. This opens the Serve as Site modal:

Audience Tiers

Grove has a tiered access model for site content:


Grant Structure


{
  "manifest_hash": "content_addressed_manifest_hash",
  "filename": "vacation.jpg",
  "file_type": "image",
  "total_size": 4521984,
  "encrypted_file_key": "base64_file_key_sealed_for_recipient",
  "granted_by": "base64_ed25519_pubkey_of_creator",
  "shared_with": "base64_ed25519_signing_pubkey_of_recipient",
  "recipient_encryption_pubkey": "base64_x25519_envelope_pubkey_of_recipient",
  "signature": "base64_ed25519_signature",
  "granted_at": "2026-03-28T15:00:00Z",
  "thumbnail": "base64_jpeg_thumbnail_or_null",
  "source_peers": ["pubkey1", "pubkey2"],
  "source_folder": "Photos",
  "rel_path": "Summer/vacation.jpg"
}

Note the two distinct recipient keys: shared_with is the recipient's canonical Ed25519 signing identity, while recipient_encryption_pubkey is their X25519 envelope key that encrypted_file_key is sealed for. granted_by is the creator's Ed25519 pubkey. source_folder / rel_path are unsigned (see Shared with me).

Verification


Feed Views

Gallery View (default)

List View

Toggle between views with ๐Ÿ–ผ๏ธ / ๐Ÿ“‹ buttons. Preference saved.


Security Notes


Tips