Using the Revaulter CLI
The Revaulter CLI (revaulter-cli) is the primary way to submit encrypt, decrypt, and sign requests. It ensures requests are End-to-End Encrypted as they are sent to the server.
Installing the CLI#
Pre-compiled binary (recommended)#
Download the latest release from GitHub Releases. Binaries are available for Linux, macOS, Windows, and FreeBSD.
Each release also includes a checksums.txt with the SHA-256 of every archive and an attestations.json holding the build provenance for each asset. To check an archive before extracting it:
gh attestation verify revaulter-<version>-<platform>.tar.gz \
--repo ItalyPaleAle/revaulterPass --bundle attestations.json to verify against the downloaded bundle instead of querying GitHub’s attestations API.
Each binary also has a CycloneDX SBOM listing the dependencies compiled into it, named revaulter-<version>-<platform>-<binary>.cdx.json. The server’s SBOM also lists the npm packages of the web client it embeds. The SBOMs are covered by checksums.txt and the attestations too.
Docker#
Mount a volume at /data to keep the trust store between runs. Pin the server’s anchor once, with -it so the CLI can ask for confirmation:
docker run --rm -it \
-v revaulter-trust:/data \
ghcr.io/italypaleale/revaulter-cli:2 trust \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIjEvery later command reuses that pin:
docker run --rm \
-v revaulter-trust:/data \
ghcr.io/italypaleale/revaulter-cli:2 encrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label my-key \
--algorithm A256GCM \
--value SGVsbG8Commands that read a file or write their output to one also need the directory mounted, for example -v "$PWD:/work". The container runs as a non-root user (UID 65532), so that user must be able to read and write those files.
Commands#
encrypt#
Submit an encryption request for approval.
revaulter-cli encrypt [flags]| Flag | Short | Required | Description |
|---|---|---|---|
--server | -s | Yes | Address of the Revaulter server (e.g. https://revaulter.example.com) |
--request-key | -k | One of --request-key or --request-key-file is required | Per-user request key (shown in the web UI after registration), or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file |
--request-key-file | One of --request-key or --request-key-file is required | Path to a file containing the per-user request key. See Reading the request key from a file | |
--user-id | Only with OIDC tokens | ID of the user the request is for, as shown in the web UI. Required when the request key is a JWT from a trusted OIDC issuer. See Authenticating with an OIDC token | |
--key-label | -l | Yes | Name of the key to use: 1 to 24 letters, digits, and _ . + - (case-insensitive) |
--algorithm | -a | Yes | AEAD algorithm identifier: A256GCM (alias aes-256-gcm) or C20P (alias chacha20-poly1305) |
--message | -m | One of --message, --input, or --json is required | The message to encrypt, as a UTF-8 string |
--input | -i | One of --message, --input, or --json is required | Path to a file whose bytes will be encrypted; use - to read from stdin |
--json | One of --message, --input, or --json is required | Path to a JSON file (or - to read from stdin) of shape {"value":"<base64url>","additionalData":"<base64url>"} (additionaldata is optional) | |
--aad | No | Additional authenticated data, base64-encoded. Not allowed with --json | |
--timeout | -t | No | Timeout for the operation (number of seconds or Go duration, e.g. 5m, 300) |
--note | -n | No | Message displayed alongside the request: up to 80 letters, digits, spaces, and . / _ - |
--output | -o | No | Write the result to a file instead of stdout |
--format | No | Output format: only json is supported | |
--insecure | No | Skip TLS certificate validation | |
--no-h2c | No | Do not attempt HTTP/2 Cleartext when not using TLS | |
--verbose | -V | No | Show debug-level logs |
Example (raw string):
revaulter-cli encrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label boot-disk \
--algorithm A256GCM \
--message "Hello, world" \
--note "boot unlock"Example (read plaintext from a file):
revaulter-cli encrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label boot-disk \
--algorithm A256GCM \
--input ./secret.binExample (JSON input):
echo '{"value":"SGVsbG8"}' | revaulter-cli encrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label boot-disk \
--algorithm A256GCM \
--json -decrypt#
Submit a decryption request for approval.
revaulter-cli decrypt [flags]| Flag | Short | Required | Description |
|---|---|---|---|
--server | -s | Yes | Address of the Revaulter server (e.g. https://revaulter.example.com) |
--request-key | -k | One of --request-key or --request-key-file is required | Per-user request key (shown in the web UI after registration), or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file |
--request-key-file | One of --request-key or --request-key-file is required | Path to a file containing the per-user request key. See Reading the request key from a file | |
--user-id | Only with OIDC tokens | ID of the user the request is for, as shown in the web UI. Required when the request key is a JWT from a trusted OIDC issuer. See Authenticating with an OIDC token | |
--key-label | -l | When not using --json | Name of the key used to encrypt the value |
--algorithm | -a | When not using --json | AEAD algorithm identifier used to encrypt the value: A256GCM (alias aes-256-gcm) or C20P (alias chacha20-poly1305) |
--value | -m | One of --value or --json is required | The ciphertext to decrypt, base64-encoded |
--tag | -g | When not using --json | Authentication tag, base64-encoded |
--nonce | When not using --json | Nonce/IV, base64-encoded | |
--aad | No | Additional authenticated data, base64-encoded. Not allowed with --json | |
--json | -j | One of --value or --json is required | Path to a JSON file (or - to read from stdin) with the output of encrypt, which includes the key label and algorithm. Mutually exclusive with --key-label, --algorithm, --value, --tag, --nonce, and --aad |
--timeout | -t | No | Timeout for the operation (number of seconds or Go duration, e.g. 5m, 300) |
--note | -n | No | Message displayed alongside the request: up to 80 letters, digits, spaces, and . / _ - |
--output | -o | No | Write the result to a file instead of stdout |
--format | No | Output format: json (default) for a JSON envelope, or raw to write the decrypted plaintext as raw bytes | |
--insecure | No | Skip TLS certificate validation | |
--no-h2c | No | Do not attempt HTTP/2 Cleartext when not using TLS | |
--verbose | -V | No | Show debug-level logs |
Example:
revaulter-cli decrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label boot-disk \
--algorithm A256GCM \
--value <base64-ciphertext> \
--nonce <base64-nonce> \
--tag <base64-tag>Example (JSON input, piping the output of encrypt):
revaulter-cli encrypt --message "secret" ... \
| revaulter-cli decrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--json - \
--format rawExample (write the plaintext to a file):
revaulter-cli decrypt \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label boot-disk \
--algorithm A256GCM \
--value <base64-ciphertext> \
--nonce <base64-nonce> \
--tag <base64-tag> \
--output /tmp/decrypted.bin \
--format rawsign#
Submit a signing request for approval. The CLI derives the bytes sent to the browser from the selected signing algorithm:
ES256: the CLI hashes the input with SHA-256 and sends the 32-byte digestEd25519: the CLI sends the raw message bytes, up to 100 KBEd25519ph: the CLI hashes the input with SHA-512 and sends the 64-byte digest
revaulter-cli sign [flags]| Flag | Short | Required | Description |
|---|---|---|---|
--server | -s | Yes | Address of the Revaulter server |
--request-key | -k | One of --request-key or --request-key-file is required | Per-user request key, or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file |
--request-key-file | One of --request-key or --request-key-file is required | Path to a file containing the per-user request key. See Reading the request key from a file | |
--user-id | Only with OIDC tokens | ID of the user the request is for, as shown in the web UI. Required when the request key is a JWT from a trusted OIDC issuer. See Authenticating with an OIDC token | |
--key-label | -l | Yes | Name of the signing key: 1 to 24 letters, digits, and _ . + - (case-insensitive) |
--algorithm | -a | Yes | Signing algorithm identifier: ES256, Ed25519, or Ed25519ph |
--input | -i | One of --input or --digest is required | Path to the message file to sign; use - for stdin. With ES256 and Ed25519ph, the CLI hashes the file locally, so it can be of any size. With Ed25519, the file is limited to 100 KB |
--digest | -d | One of --input or --digest is required | A pre-computed digest, encoded as hex or base64url. Supported only for ES256 (32-byte SHA-256) and Ed25519ph (64-byte SHA-512). Mutually exclusive with --format jws |
--format | No | Output format: json (default) for a JSON envelope with the base64url signature, jws for a compact JWS, or raw for the 64-byte signature. jws requires --input and supports only ES256 and Ed25519 | |
--jws-header | No | JSON fragment merged into the default protected header when building a JWS from --input. The alg field is always forced to ES256 or EdDSA, depending on --algorithm | |
--timeout | -t | No | Timeout for the operation |
--note | -n | No | Message displayed alongside the request: up to 80 letters, digits, spaces, and . / _ - |
--output | -o | No | Write the result to a file instead of stdout |
--insecure | No | Skip TLS certificate validation | |
--no-h2c | No | Do not attempt HTTP/2 Cleartext when not using TLS | |
--verbose | -V | No | Show debug-level logs |
Examples:
Sign a file (default output is a JSON envelope containing the base64url r || s signature):
revaulter-cli sign \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label release-signing \
--algorithm ES256 \
--input manifest.jsonSign data piped from stdin:
echo "hello" | revaulter-cli sign \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label release-signing \
--algorithm ES256 \
--input -Sign a pre-computed SHA-256 digest, for tools that already hash the data:
revaulter-cli sign \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label release-signing \
--algorithm ES256 \
--digest d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592Emit a compact JWS over a file, merging a custom kid into the protected header:
revaulter-cli sign \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label release-signing \
--algorithm ES256 \
--input manifest.json \
--format jws \
--jws-header '{"kid":"release-signing-2026"}'Write only the raw 64-byte r || s signature to a file, for pipelines that verify with a separate tool:
revaulter-cli sign \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label release-signing \
--algorithm ES256 \
--input manifest.json \
--format raw \
--output manifest.sigECDSA signatures are non-deterministic: each one uses a fresh random
k(per FIPS 186-5). Signing the same input twice produces two different signatures, and both are valid.
Ed25519signatures are deterministic for a fixed key and message.
trust#
Pin a Revaulter server’s anchor public keys in the local trust store using TOFU (Trust On First Use).
revaulter-cli trust [flags]The CLI fetches the server’s hybrid anchor bundle (ES384 + ML-DSA-87), verifies its signatures, and pins the anchor fingerprint. Subsequent commands (encrypt, decrypt, sign, ssh-agent) verify the pinned anchor and refuse to proceed if it changes unexpectedly. If the anchor is already pinned and matches, the command verifies it and exits successfully.
Run this once when first connecting to a server. In interactive use, the CLI shows the fingerprint and asks for confirmation. For non-interactive use, pass --yes to accept the key without checking the fingerprint.
The default trust store path is <user-config-dir>/revaulter-cli/trust.json (e.g. ~/.config/revaulter-cli/trust.json on Linux). Override it with the TRUST_STORE_PATH environment variable or the --trust-store flag.
| Flag | Short | Required | Description |
|---|---|---|---|
--server | -s | Yes | Address of the Revaulter server |
--request-key | -k | One of --request-key or --request-key-file is required | Per-user request key used to authenticate with the server, or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file |
--request-key-file | One of --request-key or --request-key-file is required | Path to a file containing the per-user request key. See Reading the request key from a file | |
--user-id | Only with OIDC tokens | ID of the user the request is for, as shown in the web UI. Required when the request key is a JWT from a trusted OIDC issuer. See Authenticating with an OIDC token | |
--trust-store | No | Path to the anchor trust store file (defaults to <user-config-dir>/revaulter-cli/trust.json) | |
--yes | -y | No | Accept the anchor fingerprint without prompting (for non-interactive use) |
--insecure | No | Skip TLS certificate validation | |
--no-h2c | No | Do not attempt HTTP/2 Cleartext when not using TLS | |
--verbose | -V | No | Show debug-level logs |
Example (interactive, asks for confirmation):
revaulter-cli trust \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIjExample (non-interactive, pins without asking):
revaulter-cli trust \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--yesThe
encrypt,decrypt,sign, andssh-agentcommands also accept--trust-storeto point at a non-default trust store, and--no-trust-storeto skip anchor verification entirely (equivalent to SSH’sStrictHostKeyChecking=no).
ssh-agent#
Run a local SSH agent that routes signing requests through Revaulter. Available on Unix systems only (Linux, macOS, BSD).
revaulter-cli ssh-agent [flags]The agent listens on a Unix socket. SSH clients with SSH_AUTH_SOCK pointing at the socket send each signing request through Revaulter, where the key holder approves it in the browser with a passkey. The private key never leaves the browser: the server stores only public keys, and the agent receives only signatures. The signing key for --key-label must already be published: create and publish it in the web UI under Settings → Signing keys.
Pin the server anchor with
revaulter-cli trustbefore running the agent, especially in non-interactive environments.
For a full setup walkthrough (including installing the public key in authorized_keys), see Authenticate to SSH servers.
| Flag | Short | Required | Description |
|---|---|---|---|
--server | -s | Yes | Address of the Revaulter server |
--request-key | -k | One of --request-key or --request-key-file is required | Per-user request key, or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file |
--request-key-file | One of --request-key or --request-key-file is required | Path to a file containing the per-user request key. See Reading the request key from a file | |
--user-id | Only with OIDC tokens | ID of the user the request is for, as shown in the web UI. Required when the request key is a JWT from a trusted OIDC issuer. See Authenticating with an OIDC token | |
--key-label | -l | Yes | Logical key label for the signing key |
--algorithm | -a | No | Signing algorithm: ES256 (default) or Ed25519. Ed25519ph is not supported by the SSH agent |
--socket | No | Path to the Unix socket (defaults to $XDG_RUNTIME_DIR/revaulter/ssh-agent-<key-label>.sock, or a private per-user directory under $TMPDIR if XDG_RUNTIME_DIR is unset). The socket is created with 0600 permissions | |
--comment | No | Comment attached to the SSH key (default: revaulter/<key-label>) | |
--timeout | -t | No | Per-sign-operation timeout (number of seconds or Go duration; defaults to 5 minutes) |
--note | -n | No | Extra text appended to the approval note shown in the browser (the note is always prefixed with SSH auth, leaving at most 71 characters) |
--trust-store | No | Path to the anchor trust store file | |
--no-trust-store | No | Skip anchor pinning and hybrid bundle verification (equivalent to SSH’s StrictHostKeyChecking=no) | |
--insecure | No | Skip TLS certificate validation | |
--no-h2c | No | Do not attempt HTTP/2 Cleartext when not using TLS | |
--verbose | -V | No | Show debug-level logs |
Example:
revaulter-cli ssh-agent \
--server https://revaulter.example.com \
--request-key AbCdEf0123456789GhIj \
--key-label ssh-main \
--algorithm ES256On startup the agent prints an export SSH_AUTH_SOCK=... line on stderr; evaluate it in the shell where you will use ssh:
export SSH_AUTH_SOCK='/run/user/1000/revaulter/ssh-agent-ssh-main.sock'
ssh user@prod.example.comTo list the SSH public key managed by the agent (for example to copy into ~/.ssh/authorized_keys):
SSH_AUTH_SOCK=/run/user/1000/revaulter/ssh-agent-ssh-main.sock ssh-add -LThe agent stops on Ctrl-C or SIGTERM and removes its socket on exit.
check#
Verify that a Revaulter server serves unmodified web client assets, as signed by Revaulter’s release workflow. See Verifying the web client’s integrity for the trust model and when to run this.
revaulter-cli check --server https://revaulter.example.com| Flag | Short | Required | Description |
|---|---|---|---|
--server | -s | Yes | Address of the Revaulter server |
--timeout | -t | No | Overall timeout for the check (e.g. 60s, 2m); defaults to 60s |
--min-version | No | Fail if the server runs a release older than this version (e.g. x.y.z). Without it, the CLI only warns when the server is older than the CLI itself | |
--no-rekor-fallback | No | Don’t query the Rekor transparency log when the signature bundle can’t be verified on its own | |
--insecure | No | Skip TLS certificate validation | |
--no-h2c | No | Do not attempt HTTP/2 Cleartext when not using TLS | |
--verbose | -V | No | Show debug-level logs |
version#
Print the CLI version.
revaulter-cli versionReading the request key from a file#
Every command that connects to a server accepts the request key either inline with --request-key, or from a file with --request-key-file. The two flags are mutually exclusive, and exactly one of them is required.
install -m 600 /dev/null /etc/revaulter/request-key
echo -n 'AbCdEf0123456789GhIj' > /etc/revaulter/request-key
revaulter-cli decrypt \
--server https://revaulter.example.com \
--request-key-file /etc/revaulter/request-key \
--json encrypted-key.jsonAuthenticating with an OIDC token#
If you enabled OIDC tokens in the web UI (under Settings → Request auth), you can pass a short-lived JWT from one of your trusted issuers as the request key, and your user ID with --user-id. For example, in a GitHub Actions workflow with id-token: write permission:
token="$(curl -sSf \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=https://revaulter.example.com" | jq -r .value)"
revaulter-cli sign \
--server https://revaulter.example.com \
--request-key "$token" \
--user-id "<your user ID>" \
--key-label release-signing \
--algorithm ES256 \
--input dist/myappYou can also pass the token in a file with --request-key-file. The CLI reads the file again for every request, so a long-running ssh-agent keeps working when the file is renewed in place, as with a Kubernetes projected service account token.
See Authenticating with OIDC tokens for how to set up the trusted issuers.
How it works#
When you run revaulter-cli encrypt, decrypt, or sign, the CLI:
- Fetches the user’s public encryption keys from the server
- Generates an ephemeral ECDH P-256 keypair and an ML-KEM-768 encapsulation
- Encrypts the request payload end-to-end to the user’s public keys
- Submits the encrypted request to the server
- Long-polls for the result, authenticating with the result token the server returned in step 4
- Decrypts the response envelope locally using its ephemeral private key
The server never has access to the plaintext request or response data.
Output#
By default, the CLI writes a JSON envelope to stdout, or to the file in --output. All binary values in it are base64url-encoded.
encryptwrites the ciphertext with itsnonceandtag, plus thealgorithmandkeyLabelused. Pass this JSON todecrypt --jsonas-is.decryptwrites the plaintext invalue.--format rawwrites the plaintext bytes instead, for piping into other commands or saving binary data.signwrites thesignature, with thekeyLabel,algorithm, andsigningKeyId(the JWK thumbprint of the signing key).--format jwswrites a compact JWS, and--format rawwrites the 64-byte signature.
Exit codes#
- 0: the operation completed successfully
- Non-zero: the request was denied, canceled, or expired, or an error occurred (details are printed to stderr)