Revaulter
GitHub

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#

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/revaulter

Pass --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 AbCdEf0123456789GhIj

Every 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 SGVsbG8

Commands 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]
FlagShortRequiredDescription
--server-sYesAddress of the Revaulter server (e.g. https://revaulter.example.com)
--request-key-kOne of --request-key or --request-key-file is requiredPer-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-fileOne of --request-key or --request-key-file is requiredPath to a file containing the per-user request key. See Reading the request key from a file
--user-idOnly with OIDC tokensID 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-lYesName of the key to use: 1 to 24 letters, digits, and _ . + - (case-insensitive)
--algorithm-aYesAEAD algorithm identifier: A256GCM (alias aes-256-gcm) or C20P (alias chacha20-poly1305)
--message-mOne of --message, --input, or --json is requiredThe message to encrypt, as a UTF-8 string
--input-iOne of --message, --input, or --json is requiredPath to a file whose bytes will be encrypted; use - to read from stdin
--jsonOne of --message, --input, or --json is requiredPath to a JSON file (or - to read from stdin) of shape {"value":"<base64url>","additionalData":"<base64url>"} (additionaldata is optional)
--aadNoAdditional authenticated data, base64-encoded. Not allowed with --json
--timeout-tNoTimeout for the operation (number of seconds or Go duration, e.g. 5m, 300)
--note-nNoMessage displayed alongside the request: up to 80 letters, digits, spaces, and . / _ -
--output-oNoWrite the result to a file instead of stdout
--formatNoOutput format: only json is supported
--insecureNoSkip TLS certificate validation
--no-h2cNoDo not attempt HTTP/2 Cleartext when not using TLS
--verbose-VNoShow 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.bin

Example (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]
FlagShortRequiredDescription
--server-sYesAddress of the Revaulter server (e.g. https://revaulter.example.com)
--request-key-kOne of --request-key or --request-key-file is requiredPer-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-fileOne of --request-key or --request-key-file is requiredPath to a file containing the per-user request key. See Reading the request key from a file
--user-idOnly with OIDC tokensID 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-lWhen not using --jsonName of the key used to encrypt the value
--algorithm-aWhen not using --jsonAEAD algorithm identifier used to encrypt the value: A256GCM (alias aes-256-gcm) or C20P (alias chacha20-poly1305)
--value-mOne of --value or --json is requiredThe ciphertext to decrypt, base64-encoded
--tag-gWhen not using --jsonAuthentication tag, base64-encoded
--nonceWhen not using --jsonNonce/IV, base64-encoded
--aadNoAdditional authenticated data, base64-encoded. Not allowed with --json
--json-jOne of --value or --json is requiredPath 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-tNoTimeout for the operation (number of seconds or Go duration, e.g. 5m, 300)
--note-nNoMessage displayed alongside the request: up to 80 letters, digits, spaces, and . / _ -
--output-oNoWrite the result to a file instead of stdout
--formatNoOutput format: json (default) for a JSON envelope, or raw to write the decrypted plaintext as raw bytes
--insecureNoSkip TLS certificate validation
--no-h2cNoDo not attempt HTTP/2 Cleartext when not using TLS
--verbose-VNoShow 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 raw

Example (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 raw

sign#

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 digest
  • Ed25519: the CLI sends the raw message bytes, up to 100 KB
  • Ed25519ph: the CLI hashes the input with SHA-512 and sends the 64-byte digest
revaulter-cli sign [flags]
FlagShortRequiredDescription
--server-sYesAddress of the Revaulter server
--request-key-kOne of --request-key or --request-key-file is requiredPer-user request key, or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file
--request-key-fileOne of --request-key or --request-key-file is requiredPath to a file containing the per-user request key. See Reading the request key from a file
--user-idOnly with OIDC tokensID 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-lYesName of the signing key: 1 to 24 letters, digits, and _ . + - (case-insensitive)
--algorithm-aYesSigning algorithm identifier: ES256, Ed25519, or Ed25519ph
--input-iOne of --input or --digest is requiredPath 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-dOne of --input or --digest is requiredA 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
--formatNoOutput 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-headerNoJSON 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-tNoTimeout for the operation
--note-nNoMessage displayed alongside the request: up to 80 letters, digits, spaces, and . / _ -
--output-oNoWrite the result to a file instead of stdout
--insecureNoSkip TLS certificate validation
--no-h2cNoDo not attempt HTTP/2 Cleartext when not using TLS
--verbose-VNoShow 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.json

Sign 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 d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592

Emit 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.sig

ECDSA 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.

Ed25519 signatures 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.

FlagShortRequiredDescription
--server-sYesAddress of the Revaulter server
--request-key-kOne of --request-key or --request-key-file is requiredPer-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-fileOne of --request-key or --request-key-file is requiredPath to a file containing the per-user request key. See Reading the request key from a file
--user-idOnly with OIDC tokensID 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-storeNoPath to the anchor trust store file (defaults to <user-config-dir>/revaulter-cli/trust.json)
--yes-yNoAccept the anchor fingerprint without prompting (for non-interactive use)
--insecureNoSkip TLS certificate validation
--no-h2cNoDo not attempt HTTP/2 Cleartext when not using TLS
--verbose-VNoShow debug-level logs

Example (interactive, asks for confirmation):

revaulter-cli trust \
  --server https://revaulter.example.com \
  --request-key AbCdEf0123456789GhIj

Example (non-interactive, pins without asking):

revaulter-cli trust \
  --server https://revaulter.example.com \
  --request-key AbCdEf0123456789GhIj \
  --yes

The encrypt, decrypt, sign, and ssh-agent commands also accept --trust-store to point at a non-default trust store, and --no-trust-store to skip anchor verification entirely (equivalent to SSH’s StrictHostKeyChecking=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 trust before 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.

FlagShortRequiredDescription
--server-sYesAddress of the Revaulter server
--request-key-kOne of --request-key or --request-key-file is requiredPer-user request key, or a JWT from a trusted OIDC issuer. Mutually exclusive with --request-key-file
--request-key-fileOne of --request-key or --request-key-file is requiredPath to a file containing the per-user request key. See Reading the request key from a file
--user-idOnly with OIDC tokensID 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-lYesLogical key label for the signing key
--algorithm-aNoSigning algorithm: ES256 (default) or Ed25519. Ed25519ph is not supported by the SSH agent
--socketNoPath 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
--commentNoComment attached to the SSH key (default: revaulter/<key-label>)
--timeout-tNoPer-sign-operation timeout (number of seconds or Go duration; defaults to 5 minutes)
--note-nNoExtra text appended to the approval note shown in the browser (the note is always prefixed with SSH auth, leaving at most 71 characters)
--trust-storeNoPath to the anchor trust store file
--no-trust-storeNoSkip anchor pinning and hybrid bundle verification (equivalent to SSH’s StrictHostKeyChecking=no)
--insecureNoSkip TLS certificate validation
--no-h2cNoDo not attempt HTTP/2 Cleartext when not using TLS
--verbose-VNoShow debug-level logs

Example:

revaulter-cli ssh-agent \
  --server https://revaulter.example.com \
  --request-key AbCdEf0123456789GhIj \
  --key-label ssh-main \
  --algorithm ES256

On 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.com

To 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 -L

The 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
FlagShortRequiredDescription
--server-sYesAddress of the Revaulter server
--timeout-tNoOverall timeout for the check (e.g. 60s, 2m); defaults to 60s
--min-versionNoFail 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-fallbackNoDon’t query the Rekor transparency log when the signature bundle can’t be verified on its own
--insecureNoSkip TLS certificate validation
--no-h2cNoDo not attempt HTTP/2 Cleartext when not using TLS
--verbose-VNoShow debug-level logs

version#

Print the CLI version.

revaulter-cli version

Reading 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.json

Authenticating 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/myapp

You 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:

  1. Fetches the user’s public encryption keys from the server
  2. Generates an ephemeral ECDH P-256 keypair and an ML-KEM-768 encapsulation
  3. Encrypts the request payload end-to-end to the user’s public keys
  4. Submits the encrypted request to the server
  5. Long-polls for the result, authenticating with the result token the server returned in step 4
  6. 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.

  • encrypt writes the ciphertext with its nonce and tag, plus the algorithm and keyLabel used. Pass this JSON to decrypt --json as-is.
  • decrypt writes the plaintext in value. --format raw writes the plaintext bytes instead, for piping into other commands or saving binary data.
  • sign writes the signature, with the keyLabel, algorithm, and signingKeyId (the JWK thumbprint of the signing key). --format jws writes a compact JWS, and --format raw writes 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)
Edit this page on GitHub