File Sharing
Argon supports two sharing modes from a single interface: internal sharing with other Argon users (envelope encryption, permanent access) and external sharing with anyone via a magic link (split-key AES-256-GCM, time-limited, no account required).Internal Sharing (Argon User to Argon User)
When sharing a file with another Argon user, the file is encrypted with envelope encryption — the same pattern used for vault entries. The recipient gets the file directly in their Argon account.How It Works
Properties
- End-to-end encrypted — The server stores an opaque encrypted blob. It cannot read the file.
- Permanent — Once accepted, the file lives in the recipient’s vault like any other entry. It persists until they delete it.
- Revocable before acceptance — The sender can revoke an unaccepted share.
- Audited — Both the share creation and acceptance are logged in the audit trail.
Recipient Flow
- A notification badge appears: “N new shared files.”
- The recipient sees the sender’s name, filename, file size, and optional message.
- Accept — The file is saved into the recipient’s chosen vault and folder.
- Decline — The share and its encrypted data are permanently deleted.
External Sharing (Magic Link for Anyone)
When sharing with someone who doesn’t have an Argon account — a client, a contractor, a vendor — Argon generates a magic link that works in any browser. No app install, no registration, no account.How It Works
Split-Key Security
The 256-bit encryption key is split in half:| Half | Location | Sent to Server? |
|---|---|---|
key_server (128 bits) | Stored in Argon’s database | Yes — the server holds this half |
key_fragment (128 bits) | In the URL after # | Never — the browser never sends the #fragment in HTTP requests |
- A stolen database dump doesn’t expose shared files (missing
key_fragment). - An intercepted link doesn’t expose the file (missing
key_server). - Even Argon server administrators cannot decrypt external shares without the link.
Share Options
| Option | Default | Description |
|---|---|---|
| Expiry | 7 days | Auto-delete after this duration. Max 30 days. |
| Max downloads | Unlimited | Set to 1 for burn-after-reading. |
| Password | None | Recipient must enter the password before downloading. Hashed with Argon2id on the server. |
The Download Page
The magic link serves a self-contained HTML page with all CSS and JS inlined. No external resources, no CDN, no fetches for scripts. It runs entirely in the browser using the native WebCrypto API. The page:- Reads
key_fragmentfrom the URL#fragment(never sent to the server). - Fetches file metadata (filename, size, expiry countdown).
- If password-protected, shows a password prompt.
- On “Download” click, fetches the encrypted blob +
key_server. - Reconstructs
file_key = key_server || key_fragment. - Decrypts using WebCrypto AES-256-GCM.
- Triggers a browser download of the plaintext file.
//go:embed — no static file serving infrastructure needed.
Why AES-256-GCM for External Shares?
Argon uses XChaCha20-Poly1305 for all vault encryption, but external shares use AES-256-GCM because:- The browser’s WebCrypto API natively supports AES-GCM but not XChaCha20-Poly1305.
- Native WebCrypto means zero bundled crypto libraries — the download page stays small and auditable.
- Each share has a unique random key, so AES-GCM’s smaller nonce (96-bit vs 192-bit) is not a concern.
Airgapped Environments
Both sharing modes work in fully airgapped networks:- Internal shares use the standard gRPC connection the desktop app already has.
- External magic links point to the Argon server’s internal address (e.g.,
https://10.0.1.5:50052/s/abc123#key). The recipient just needs browser access to the server on the network — no internet, no DNS, no external services.
ARGON_PUBLIC_URL and is used to construct share links. In airgapped environments, this is the internal IP or hostname.
HTTP Endpoints
External share routes are served on the existing gRPC-Web port (50052) alongside the existing gRPC-Web and/ca.pem handlers:
| Route | Method | Auth | Purpose |
|---|---|---|---|
/s/<token> | GET | None | Serve the self-contained download page |
/s/<token>/meta | GET | None | Return JSON metadata (filename, size, password required, expiry) |
/s/<token>/download | POST | Optional password | Return key_server + encrypted blob as binary |
Lifecycle & Cleanup
A background job runs hourly to clean up:- Expired shares — External shares past their
expires_atare permanently deleted. - Consumed shares — Shares where
download_count >= max_downloadsare purged. - Accepted internal shares — Share records older than 30 days where the file has already been moved to the recipient’s vault.
Storage Limits
| Setting | Default | Description |
|---|---|---|
| Max file size per share | 100 MB | Reject uploads over this size |
| Max total share storage | 1 GB | Combined size of all active encrypted share blobs |
| Default external expiry | 7 days | Applied when no expiry is specified |
| Max external expiry | 30 days | Upper bound on sender-specified expiry |

