A tiny, security-hardened curl wrapper for curlhub.sh.
It talks only to curlhub.sh over TLS, keeps your API token off the
command line, and renders upgrade notices to stderr so piped and binary
output stays byte-for-byte exact.
curl -fsSL https://curlhub.sh/download | bashSystem-wide (into /usr/local/bin; needs write access — never auto-sudo):
curl -fsSL https://curlhub.sh/download | bash -s -- --system/download serves a ~40-line, auditable bootstrap that fetches the real,
tag-pinned installer from public GitHub (tuxxin/curlhub.sh-cli) over TLS and
sha256-verifies the wrapper before installing. The trust root is
"GitHub + the tag", not the curlhub.sh box. Always use the -fsSL https://
form so an error page can never be piped into your shell.
Offline / local install (used by the test suite before the GitHub repo exists):
CURLHUB_CLI_SRC=./cli ./cli/install.sh # local dir with curlhub + curlhub.sha256If your install dir isn't on PATH, the installer prints the exact
export PATH=… line to add — it never edits your rc files.
curlhub uuid # GET https://curlhub.sh/uuid
curlhub status/418 # path segments pass straight through
curlhub # GET / (the MOTD)
printf 'hello' | curlhub b64 # pipe stdin into a tool
curlhub json < data.json # same, via redirection
curlhub uuid -v -o out.txt # extra curl flags (from a vetted allowlist)curlhub <path> [curl-args…] — the first argument is a curlhub path (or a
subcommand, below); everything after it is forwarded to curl from a vetted
allowlist of safe flags (see Security notes). Common flags (-v -s -o -w -H -m -L -d -F -T -X --compressed …) pass through; flags that could redirect the
request off curlhub.sh or weaken TLS are refused.
| Command | Action |
|---|---|
curlhub help [tool] |
Self-discovery: the MOTD, or per-tool help. |
curlhub version [--check] |
Print the baked version; --check also reports the server's latest. |
curlhub update [--system] |
Re-run the TLS bootstrap → install the latest release. |
curlhub login |
Paste an API token (hidden input); validated against /api/v1/whoami before it is stored. |
curlhub logout |
Shred/remove the stored token. |
curlhub token path |
Print the token file path. |
curlhub token show |
Print only the masked token prefix. |
curlhub token status |
Query /api/v1/whoami with the stored token. |
curlhub completion bash|zsh|fish |
Emit a shell-completion script. |
curlhub push <file>… |
Upload up to 3 files to /u; prints the one-time link, QR, and pull command. |
curlhub pull <id> [-o FILE | -d DIR] |
Download a /u blob; the server names the file (-OJ), or choose -o/-d. One shot, no resume. |
curlhub push report.pdf # upload one file -> prints link + QR + pull command
curlhub push a.txt b.log c.csv # up to 3 files -> bundled server-side into curlhub-<id>.tgz
curlhub pull K2m9Qx # download; the server names the file (Content-Disposition, via -OJ)
curlhub pull K2m9Qx -o out.pdf # save under a name you choose
curlhub pull K2m9Qx -d ~/Downloads # save into a directorypush is a thin front-end over curl -F 'file[]=@…' https://curlhub.sh/u: each argument must be a
regular, readable file, and a path containing ;, a newline, or a leading - is refused (curl
-F would otherwise read ;headers=@…/;type=… sub-parameters out of the path). Up to 3 files per
upload — the same hardened /u endpoint scans every file and enforces the 5 MB-per-file cap.
pull does exactly one GET (no --retry, no resume): a /u link is single-download, so once
it has been fetched — by you or anyone — it is gone, and a second pull returns 404. The id is
validated to [A-Za-z0-9], ≤ 16 chars before any request is made. By default -OJ lets the server
name the file (curl won't overwrite an existing file of that name); use -o/-d to control where it
lands.
curlhub completion bash > ~/.local/share/bash-completion/completions/curlhub
curlhub completion zsh > "${fpath[1]}/_curlhub"
curlhub completion fish > ~/.config/fish/completions/curlhub.fish- TLS by construction. The wrapper only ever requests
${CURLHUB_BASE_URL}/<path>(defaulthttps://curlhub.sh) with--proto '=https'. A path argument containing a scheme (://), a host, or..is rejected — there is no code path that emitshttp://or leaves the curlhub.sh origin. - Vetted passthrough (fail-closed). Because
curlapplies a single-H(your bearer token) to every URL operand, bundles short flags (-sK≡-s -K), abbreviates long ones, and can load a config file (-K) or disable TLS (-k), the wrapper does not forward args blindly. It parses them and permits only an allowlist of safe curl flags; anything else — a second URL,-K/--config,--url,-k/--insecure,--resolve/--connect-to,-x/--proxy, a bare operand — is refused. This guarantees your token and any piped body can only reach curlhub.sh over verified TLS. It is stricter than rawcurl; for arbitrary curl behavior, callcurldirectly. - Your token never touches argv /
ps/ logs.loginreads it withread -rsfrom the terminal (never a positional arg). Each authenticated request supplies it ascurl -H @<file>, where the file is amktemp0600temp written by theprintfbuiltin (noexec, so nothing shows in the process table) and removed on a trap. It is stored at$HOME/.config/curlhub/token(file0600, dir0700,umask 077), never echoed, andtoken showprints only a masked prefix. It is written only after/api/v1/whoamireturns200 authenticated:true. - Upgrade nudge is body-safe. The server advertises the latest/min version
in the
X-Curlhub-Cli-Latest/-Minresponse headers; the wrapper renders at most one notice to stderr (≤ 1 / 24h). No response body byte,Content-Length, status, or exit code is ever changed — piped, JSON, and binary (/u) streams stay exact. Silence it withCURLHUB_NO_UPDATE_NOTICE=1(also suppressed underNO_COLOR). - Integrity (v1). The installer verifies the wrapper against a
curlhub.sha256fetched from the same GitHub tag → defends transport/CDN tampering, not a GitHub account/tag rewrite. A minisign/cosign signature with a bootstrap-baked public key is the planned v1.1 hardening. GitHub tag protection is mandatory.
The server cannot tell an interactive terminal from a pipe — curl sends no such signal — so it
decides by User-Agent, and a plain curl … | less receives raw escape sequences. The wrapper closes
that gap from the client side: it inspects its own stdout and asks the server to skip ANSI
(X-No-Color: 1) whenever the output is not going to a terminal:
curlhub man tar | less # piped -> no escapes
curlhub uuid > id.txt # redirected -> no escapes
curlhub uuid -o id.txt # -o/-O -> no escapes (stdout is still a TTY; the body is not)
NO_COLOR=1 curlhub uuid # no-color.org
CURLHUB_COLOR=always curlhub qr foo | less -R # force colour back onValues are emitted bare regardless of colour: ID=$(curlhub uuid) is a usable UUID, and
curlhub json < data.json | jq parses.
When you pipe into a tool and pass no data/upload flag, the wrapper
auto-appends --data-binary @-, which flips curl to POST. That is what
POST-accepting tools want (b64, hash, json, jwt, md, ssl, p, u).
Piping into a GET-only tool (e.g. uuid) therefore yields 405 — pass
your own flag (e.g. -G) or don't pipe. Auto-stdin is suppressed whenever you
supply any of -d/--data*, -F/--form, -T/--upload-file, or -G.
| Variable | Purpose |
|---|---|
CURLHUB_BASE_URL |
Override the base URL (dev/test only; default https://curlhub.sh). |
CURLHUB_NO_UPDATE_NOTICE |
Suppress the upgrade nudge. |
NO_COLOR |
Suppresses the upgrade nudge and asks the server for no ANSI (no-color.org). |
CURLHUB_COLOR |
always keeps server colour even when output is piped or redirected. |
CURLHUB_CLI_SRC |
Installer only: local dir / file / URL base for offline installs. |
REPO, REF, PREFIX |
Installer only: source repo, pinned tag, install dir. |
Built by Tuxxin LLC. Part of curlhub.sh — developer tools
you can curl. Issues and ideas: curlhub.sh/contact.
MIT © Tuxxin LLC. See LICENSE.