CLI reference
Every gone command and flag, how it chooses a server, its exit codes and its JSON output. To install gone and send a first secret, start with the CLI guide.
Commands
gone send
Encrypts a message and/or files and uploads them.
gone send [flags]- The message is read from standard input. Pipe it in, or type it and press Ctrl-D (Ctrl-Z then Enter on Windows). There is deliberately no flag for the message text, so it never lands in shell history or the process list.
--message-file PATHreads the message from a regular file instead, byte for byte (a trailing newline is kept), and standard input is not read at all. Pipes, FIFOs and devices are refused; pipe those into standard input instead.- To send only files, give standard input nothing:
gone send -f report.pdf </dev/null. - The message plus files must fit within the server’s size limit (
GONE_MAX_BYTES, 10 MiB by default); the server also enforces its own minimum and maximum TTL. Independently of the server,gonerefuses inputs over 64 MiB before encrypting anything (exit 2). - An attachment’s type is guessed from its file extension. Its name is the file’s base name.
- Server policy. Before reading anything,
gonefetches the server’s policy (GET /api/policy, see Policies). If the server requires a passphrase and no--passphrase-*flag is given, or more files are attached than it allows,goneexits 2 with error kindpolicyand nothing is read, prompted for or sent. A server older than v3.12 has no policy route and is treated as having no policy. On a server that requires sign-in,gone sendandgone requeststop the same way: the CLI can’t sign in through the operator’s proxy yet. - At most one
--passphrase-*flag may be used. A passphrase must be at least 8 characters.--passphrase-generateprints five random words from the EFF short wordlist, joined in CamelCase (about 51 bits), to standard error, so a redirected standard output only captures the link.
Example with files and a passphrase:
gone send -f id_ed25519 -f id_ed25519.pub --passphrase-generate --ttl 2h </dev/nullgone get
Downloads, decrypts and deletes a secret.
gone get <link|-> [flags]- The link must point at the server you use, or one you allow with
--allow-origin; otherwise nothing is sent (see Which servers a link may reach). - The message is written to standard output, attachments to
--out. The secret is deleted from the server only after everything has been decrypted and written. - A link of
-is read from standard input (surrounding whitespace is ignored), so it stays out of the process list and shell history:gone get - < link.txt. --message-out PATHwrites the message to a new file instead of standard output, with mode0600, byte for byte (--rawhas no effect on it). An empty message still creates an empty file. The file is written to a temporary name in the same directory, synced, then hard-linked into place, soPATHeither does not exist or is complete. It is never overwritten: ifPATHexists, even as a dangling symlink,getfails with exit 8. On Windows the mode is not applied; the file inherits the directory’s permissions.--outmust be an existing, writable directory.--message-outmust name a file that does not exist yet, in an existing directory that is writable and supports hard links. Both are checked before the link is opened, so a bad path never burns the secret. The same goes for--passphrase-filewith a link that has no passphrase (exit 2).- If writing fails after the link was opened (for example, an attachment cannot be saved), whatever this run wrote is removed and the deletion is not confirmed. The secret is still lost: the server has already handed it out once.
- Existing files are never overwritten. If
report.csvexists, the attachment is saved asreport (1).csv, and so on. Files are created exclusively with mode0600inside the output directory, and symlinks are not followed out of it. - Attachment names are sanitized as in protocol.md §6.1; Windows reserved names (
CON,NUL, …), trailing dots and spaces, and the characters<>:"|?*(replaced with_on every platform) are also made safe. A leading.or-is replaced with_(.zshenvis saved as_zshenv), so a sender cannot plant a hidden dotfile or a name that looks like a command-line option. - For a passphrase-protected link,
goneprompts on the terminal (up to three tries). The ciphertext is downloaded once and every try runs against that copy. An empty or invalid entry (over 1024 bytes, or not UTF-8) uses up a try, like a wrong passphrase. Before the third try,gonewarns that it is the last one: the link has already been opened, so a third wrong passphrase loses the secret for good (exit 4, and the error says so). Ctrl-D cancels at once, and because the link has already been opened, cancelling loses the secret. In scripts, use--passphrase-file; exactly one trailing newline (\nor\r\n) is removed from the file. That gives a single try: opening the link has already started the secret’s deletion, so a wrong passphrase in the file means the secret is gone, and the error says it can no longer be recovered. - If the download is cut off after the server handed out the secret,
gonefetches the same ciphertext again with its claim token, up to twice, while the claim lease lasts (two minutes by default; see protocol.md §8.2). Nobody else can fetch it in that time. - On a terminal, control characters in the message are escaped so a sender cannot inject terminal escape sequences.
--rawturns this off. When standard output is a pipe or file, the message is written byte-for-byte. With--json, DEL, C1 and bidirectional-override characters are always written as\uXXXXescapes, so the output is safe on a terminal and still decodes to the original text. (A message that is not valid UTF-8 can’t round-trip through JSON: invalid bytes become U+FFFD. Use plain output for binary messages.)
gone status
gone status <manage-link|-> [-s URL] [--allow-origin ORIGIN]... [--json] [--insecure] [--timeout DURATION]Prints State: pending, with creation and expiry times, while the secret is still waiting. A manage link of - is read from standard input. Once it has been opened, revoked or has expired, the server answers “not found” (exit 3); it does not reveal which.
gone revoke
gone revoke <manage-link|-> [-s URL] [--allow-origin ORIGIN]... [--json] [--insecure] [--timeout DURATION]Deletes the secret. Revoking an already-gone secret exits 3. Revoke is reliable only before the recipient opens the link: someone who has already downloaded the ciphertext keeps it.
gone request
gone request [-l TEXT] [-t DURATION] [-s URL] [--wait [-o DIR] [--message-out PATH] [--raw]] [--json]Asks someone for a secret and prints a request link to send them, then exits. A key pair is made on this machine: the private key and the request’s manage token are saved on this device (see Requests on this device) and never leave it. The server learns only how long to wait. The person with the secret answers on that link in a browser, or with gone reply, and only this device can open the reply, once.
-l, --label TEXTis a note so you can tell requests apart. Up to 80 characters are kept; a longer label is cut to 80. It stays on this device and is never sent.-t, --ttlis how long the link works (default1h). A reply is then kept for the same length of time, counted from when it arrives.-s, --serverpicks the server as forsend.--waitwaits for the reply and opens it, taking the same output flags asrequest open. The request is saved first, so Ctrl-C stops waiting but keeps it; resume withgone request open ID --wait.- The request link can’t open anything; it only lets someone send you one reply. Send it to the right person.
gone request list
gone request list [--offline] [--json] [--insecure] [--timeout DURATION]Lists the requests saved on this device, newest first, and asks each one’s server for its state:
ID LABEL STATE ASKED EXPIRES
3f9a1c2e staging DB reply ready 2026-08-10 17:30 UTC 2026-08-10 19:30 UTC
b7d0e411 - waiting 2026-08-10 17:12 UTC 2026-08-10 18:12 UTCA request the server no longer has (expired, cancelled, or already opened) is shown once as gone and then forgotten. If a server can’t be reached the state is unknown. --offline shows the last known state without contacting any server. Checking state is read-only on the server.
gone request open
gone request open <id> [--wait] [-o DIR] [--message-out PATH] [--raw] [--json] [--insecure] [--timeout DURATION]Downloads the reply, decrypts it with the saved key, and prints the message and saves attachments exactly like gone get. It then deletes the reply from the server and forgets the request here. <id> is the full ID or a unique prefix of at least 4 characters. Without --wait, a request that has no reply yet exits 3 (not_ready); with it, gone checks every 10 seconds until the reply arrives or the request expires.
gone request cancel
gone request cancel <id> [--json] [--insecure] [--timeout DURATION]Cancels the request, or deletes a reply before you open it, and forgets it here. The link stops working at once. Cancelling a request that is already gone exits 3.
gone reply
gone reply <request-link|-> [-f FILE]... [--message-file PATH] [-s URL] [--allow-origin ORIGIN]... [--json] [--insecure] [--timeout DURATION]Answers someone’s request. The link is checked with the server first, so if the request was already answered, cancelled, or has expired, gone exits 3 without reading the message. The message comes from standard input or --message-file, plus up to 10 attachments (fewer if the server’s policy says so), exactly as for send. A reply never needs a passphrase, even on a server that requires one for send. It is encrypted to the requester’s public key from the link: not even you can open it afterwards. Prints Sent. Only the requester can open it. and an Expires: line with when the reply is deleted if the requester never opens it. A request link of - is read from standard input.
Only answer a request link you trust: anyone can make one, so check it came from the person you expect.
Requests on this device
Requests are saved in requests.gob in the gone config directory (~/.config/gone on Linux, ~/Library/Application Support/gone on macOS, %AppData%\gone on Windows), readable only by you (0600, in a 0700 directory). Each entry holds the request ID, its server, your label, the manage token, the private key, the request link and the last known state. That is as private as a browser’s site storage, which is where the web UI keeps its requests: anyone who can read the file as you can open a reply that arrives.
The file is internal to gone: it is a binary (Go gob) file, not meant to be opened, edited or copied. Use gone request list to see what’s in it. Every change takes a lock (requests.lock), writes a new file and renames it into place, so several gone commands running at once never lose a request and the file is never half-written. Like ssh with a private key, gone refuses to use the file (exit 8) if other users can read or write it, if it belongs to someone else, or if it is a symlink, and refuses a config directory that others can write to. It says why and prints the chmod that fixes it, rather than fixing it silently, because the keys may already have been exposed. (Windows protects your profile with ACLs instead, so these checks don’t apply there.) An entry is removed when its reply is opened, the request is cancelled, or it expires. A request made here can only be opened here, and one made in a browser can only be opened in that browser.
gone set server
gone set server <url> [--insecure]Saves the default server for send and request. The value must be a bare origin such as https://gone.example.com. The file is gone/config.json under your user configuration directory ($XDG_CONFIG_HOME, or ~/.config if unset, on Linux; ~/Library/Application Support on macOS; %AppData% on Windows), written with mode 0600. On Linux and macOS, gone refuses to read a config file that other users can write to or that belongs to someone else (exit 8, naming the file), because it decides which server every command trusts. Fix it with chmod 600 on the file.
get, status, revoke and reply use the server named in the link, but only if it is the server you use; see Which servers a link may reach. request list, request open and request cancel use the server each saved request was made on.
gone version, gone help
gone version prints the build version. gone help <command> prints the help for one command.
Server selection and transport
send and request pick their server from, in order: --server, the GONE_SERVER environment variable, the saved config, then https://app.getgone.dev. When --server or GONE_SERVER overrides a server saved with gone set server, a one-line notice on standard error names the server in use (Using https://… (from GONE_SERVER), not the configured server https://….).
Which servers a link may reach
A link names its own server, and anyone can make a link that points anywhere. Opening a link sends a request that tells its server your IP address and when you opened it, so get, status, revoke and reply only contact a link’s server if it is:
- the server you use, chosen exactly as for
send:--server,GONE_SERVER, the saved config, thenhttps://app.getgone.dev; or - named with
--allow-origin ORIGINon this command. Repeat the flag or separate origins with commas. Each value must be a bare origin such ashttps://gone.example.com; it applies to this command only.
Any other link is refused before anything is sent, with exit 2 (JSON error kind untrusted_origin). The message names both servers, never the link’s ID or key:
gone: refused: this link is for https://gone.example.com, but no server is set, so gone only trusts the default https://app.getgone.dev; nothing was sent. If you trust https://gone.example.com, add --allow-origin https://gone.example.com for this command, or make it your server with: gone set server https://gone.example.comIf you use your own server, run gone set server https://your.server once and its links just work. Origins are compared after lowercasing and dropping a default port, so https://Gone.Example:443 and https://gone.example are the same server.
Transport
The standard proxy variables (HTTPS_PROXY, HTTP_PROXY, NO_PROXY) are honored for every https:// request. An http:// server (below) is always contacted directly, never through a proxy.
- HTTPS only.
http://origins and links are refused on every command. The one exception is a server on this machine, namedlocalhostor a literal loopback address (127.0.0.0/8or::1), with--insecure, for local testing; browsers make the same exception. Other names, including*.localhost, are looked up in DNS like any host, so they are refused. Any otherhttp://server is refused even with--insecure. - No redirects. A redirect is treated as a server error, so a secret is never sent to, or fetched from, an origin other than the one you named.
- Bounded responses. Responses larger than the protocol allows are rejected before they are read.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Internal error, or interrupted (Ctrl-C) |
| 2 | Usage error: bad flag, bad link, bad server URL, weak passphrase, a link for a server you don’t use (untrusted_origin), or input the server’s policy refuses (policy) |
| 3 | Not found: already opened, revoked, expired, or never existed; for request open without --wait, no reply yet |
| 4 | Wrong passphrase (or the link is damaged); the secret is lost |
| 5 | Rate limited by the server |
| 6 | Integrity failure: the ciphertext or payload failed authentication |
| 7 | Network or server error, response too large, or deletion not confirmed |
| 8 | Local I/O error: reading input files, writing attachments or config, or an unsafe config file |
The server cannot tell “already opened”, “revoked” and “expired” apart from the outside, so they share code 3. A request with no reply yet is also exit 3, with error kind not_ready in JSON, so a script can tell it from not_found.
If get decrypts a secret but the server does not confirm its deletion, gone prints the message and attachments and then exits 7. The server’s claim lease expires on its own and the secret is removed by the janitor.
JSON output
With --json, results are a single line of JSON on standard output. Times are RFC 3339 in UTC.
send:
{"link":"https://…/secret/ID#v1:KEY","manage_link":"https://…/manage/ID#TOKEN","id":"ID","expires_at":"2026-08-10T18:30:00Z"}get:
{"message":"hunter2","files":[{"name":"report.csv","path":"out/report (1).csv","type":"text/csv","size":1024}]}This contains the plaintext. Anything that captures standard output (a log, a CI job, an AI agent’s context) captures the secret. To keep it out, use --message-out.
get --message-out: the message key is absent, and message_file gives the path as you passed it and the size in bytes:
{"message_file":{"path":"creds/restic.pw","size":17},"files":[]}status:
{"state":"pending","created_at":"2026-08-10T17:30:00Z","expires_at":"2026-08-10T18:30:00Z"}revoke:
{"revoked":true}request:
{"id":"ID","link":"https://…/reply/ID#v3:PUBLIC_KEY.FILL_TOKEN","label":"staging DB","expires_at":"2026-08-10T18:30:00Z"}request --wait prints two lines: the line above as soon as the request is made, then the request open result once the reply arrives.
request list (an array, newest first; state is waiting, ready, gone or unknown):
[{"id":"ID","label":"staging DB","state":"ready","server":"https://app.getgone.dev","created_at":"…","expires_at":"…","link":"https://…/reply/ID#v3:…"}]request open: the same as get (or get --message-out). request cancel: {"cancelled":true}. reply: {"sent":true,"expires_at":"…"}.
Errors are one line of JSON on standard error, and the exit code is still set:
{"error":"rate_limited","message":"rate limited; retry after 30s","retry_after":30}error | Exit |
|---|---|
internal | 1 |
interrupted | 1 |
usage | 2 |
untrusted_origin | 2 |
policy | 2 |
not_found | 3 |
not_ready | 3 |
wrong_passphrase | 4 |
rate_limited | 5 |
integrity | 6 |
network | 7 |
server | 7 |
too_large | 7 |
not_confirmed | 7 |
io | 8 |
retry_after (seconds) is present only for rate_limited, and only when the server supplied it. --passphrase-generate still prints the passphrase to standard error as plain text, outside the JSON.