Skip to content
garv-sanveriaPublic
forked from getsotto/sotto

About

end to end encrypted secret sync for developer teams. encrypt locally; the server only ever stores ciphertext.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

783 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English | Español | Français | Deutsch | Português (BR)

Sotto

End-to-end encrypted secret sync for developer teams. Stop Slacking your .env.

Warning

Sotto is pre-1.0 and has not had a third-party cryptographic audit. It works end to end, but should not yet be trusted with critical production secrets. See SECURITY.md.

Sotto is built around one Rust crypto implementation shared by the native CLI and the browser client through WebAssembly. The server stores and synchronises encrypted data without ever receiving plaintext secrets or usable keys.

Current status

The end-to-end flow works: encrypt locally, sync ciphertext, decrypt on another device or in the browser, and share a single secret via a one-time link. Teams work end to end too: organisations with roles, per-member environment grants, key rotation on member removal, machine tokens for CI, and lost-key account recovery.

Component Available now
Crypto core KDF, XChaCha20-Poly1305 AEAD + AAD, key wrapping, X25519 sealed-box grants, the environment vault hierarchy, data-key rewrap (rotation), share-link crypto, and key encoding - with native↔WASM golden vectors
CLI init, local secret management, run-style injection, login/push/pull sync, new-device setup, share; teams: org create/ls/invite/members/remove, grant, clone, rotate, machine token create/ls/revoke (with SOTTO_TOKEN mode for CI), lost-kit reset
Server OAuth login + sessions, account + snapshot sync (versioned writes, ETag), orgs + memberships + roles, per-member vault-key grants, transactional key rotation, machine tokens, account reset, and share links - ciphertext only
Web Login (cookie session), in-browser unlock + vault decryption via your own grant, one-time share create/receive, and a team panel: orgs, members, invite by email, share an environment with a member

Install

Prebuilt, signed binaries for macOS (Apple Silicon + Intel), Linux (x86_64 + ARM64), and Windows x86_64:

curl -fsSL https://raw.githubusercontent.com/getsotto/sotto/main/install.sh | sh
irm https://raw.githubusercontent.com/getsotto/sotto/main/install.ps1 | iex

The installer verifies the archive's SHA-256 checksum - and its Sigstore signature, when cosign is installed - before installing (~/.local/bin on macOS/Linux, %LOCALAPPDATA%\sotto\bin on Windows). Prefer to look first? Grab an archive from the releases page and verify it manually per SECURITY.md, or build from source (see Developing).

Shell completions

sotto completions <shell> prints a completion script to stdout. For example:

# bash
mkdir -p ~/.local/share/bash-completion/completions
sotto completions bash > ~/.local/share/bash-completion/completions/sotto

# zsh (add the directory to fpath in ~/.zshrc first)
mkdir -p ~/.zfunc
sotto completions zsh > ~/.zfunc/_sotto

# fish
mkdir -p ~/.config/fish/completions
sotto completions fish > ~/.config/fish/completions/sotto.fish

Release tarballs and zips also contain the matching completions/sotto.* files if you prefer to copy them directly.

GitHub Actions

For GitHub Actions, use the Sotto Setup action to install an exact CLI release and verify its checksum and Sigstore bundles before making sotto available to later steps:

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
      - uses: getsotto/sotto-action@v1.1.0
        with:
          sotto-version: v0.9.0
      - run: sotto run -- npm test
        env:
          SOTTO_SERVER: ${{ vars.SOTTO_SERVER }}
          SOTTO_TOKEN: ${{ secrets.SOTTO_TOKEN }}

The action ref and sotto-version are independent. Keep sotto-version as an exact vX.Y.Z release. Set the optional SOTTO_SERVER repository variable for a self-hosted server. See the action documentation for matrix, Windows, and reusable-workflow examples.

Quick start

Want a runnable demo? sotto-example walks through local secret injection with a Python GIF and copyable steps, plus tiny examples in JavaScript, TypeScript, Java, C#, PHP, Go, and C++. No account required; instructions cover macOS, Linux, and Windows.

sotto init                   # create your identity + first project - SAVE the printed Emergency Kit
sotto set DATABASE_URL       # hidden prompt; encrypted locally before it ever touches disk
sotto import .env            # optional: pull in an existing file, still encrypted locally
sotto export --format dotenv --reveal   # print a .env; refuses a terminal without --reveal
sotto run -- npm start       # inject the environment's secrets into any command
sotto login && sotto push    # optional: sync ciphertext via the hosted instance (getsotto.co.uk)
sotto get DATABASE_URL -c    # copy a secret without printing it; clipboard clears after 45s when unchanged
sotto share DATABASE_URL     # one-time link; copied automatically in an interactive terminal
sotto share DATABASE_URL --views 3
sotto share DATABASE_URL --expire 3600   # lifetime in seconds

By default, a share allows one view and has no expiry; the link burns after the last view.

Use --env to select an environment for one command without changing the project's default:

sotto run --env staging -- npm test
sotto ls --env staging

--env lasts for that command only; sotto env use changes the default.

Export writes plaintext, so it needs --reveal on a terminal, just like sotto get. Use sotto share --no-copy to disable interactive copying, or --copy to request it explicitly. Clipboard clearing is best-effort: replacing the clipboard protects the newer content, while clipboard managers, suspension, or a terminated helper may retain a history copy.

sotto login uses the hosted instance at getsotto.co.uk unless you point it elsewhere with --server <url> (see Deploying to run your own). Either way the server only ever stores ciphertext - the web vault at the same address decrypts in your browser, with keys that never leave your devices.

Working with a team:

sotto org create acme                      # prints the org id
sotto init --org <org-id>                  # an org-owned project
sotto org invite <org-id> dev@example.com  # invite an existing Sotto user
sotto grant <user-id>                      # share the active environment (they run `sotto clone`)
sotto token create --name ci               # SOTTO_TOKEN: run/export in CI, no password needed

Machine tokens expire. A new token lasts 90 days unless you pass --expires-in-days with anything from 1 to 365, and sotto token ls shows when each one ends. Two weeks before that, sotto run and sotto export print a warning in the CI log. To replace a token, create a new one, update the CI secret, then revoke the old one with sotto token revoke.

Output themes

The CLI ships five built-in themes: nord (the default), sordino, terminal, monochrome, and tokyo-night. Manage them with sotto theme:

sotto theme ls        # list available themes; the active one is marked
sotto theme set nord  # save a preference for later commands
sotto theme current   # print the theme this shell resolves to

--theme <name> picks a theme for one command. SOTTO_THEME sets it for a shell, and sotto theme set saves it. Precedence is --theme, then SOTTO_THEME, then the saved preference, then nord; an unknown name warns on standard error and falls back to nord.

Styling applies to interactive terminals only. --plain, a non-empty NO_COLOR (the no-color.org contract), CI environments, piped standard output, and redirected standard input each suppress colour and decoration while keeping the selected palette, so scripts and logs read plain text.

Custom themes are TOML files in the themes directory inside your platform data directory: ~/Library/Application Support/sotto on macOS, %APPDATA%\sotto on Windows, and $XDG_DATA_HOME/sotto or ~/.local/share/sotto on Linux (SOTTO_DATA_DIR moves that directory). Each *.toml file needs the tokens below and takes its name from a name field, or from the filename when the field is absent; a file that fails to parse is skipped. Colours accept hex values, ANSI colour names, ansi(<index>) indices, and default.

bg = "#120024"
fg = "#ffffff"
accent = "#ff007f"
success = "#00ff66"
warning = "#ffaa00"
error = "#ff0033"
muted = "#775588"
border = "#331144"

Saved as synth.toml, that file adds a synth theme. Apply it with sotto theme set synth, or for one command with sotto --theme synth <command>.

Another device

sotto login                  # same account as the first machine
sotto setup                  # unpack the Emergency Kit onto this device
sotto pull                   # download the ciphertext you already pushed

You need the Emergency Kit printed by sotto init; without it, a new device cannot decrypt the vault.

Architecture

CLI (native) ─────┐
                  ├── sotto-core ── versioned encrypted data
Web client (WASM) ┘                         │
                                            ▼
                                  sync/API server
                                  (ciphertext only)

The workspace contains four crates:

  • crates/core - shared cryptographic types and, the complete crypto implementation.
  • crates/cli - the sotto command-line interface and primary native client.
  • crates/server - the Axum-based synchronisation API.
  • crates/wasm - wasm-bindgen bindings that expose the core to web clients.

Prerequisites

  • Rustup with stable Rust 1.89 or newer
  • The clippy and rustfmt components
  • The wasm32-unknown-unknown target

The checked-in rust-toolchain.toml asks Rustup to install the required components and target automatically.

Developing

Clone the repository, then build and test the complete workspace:

git clone https://github.com/getsotto/sotto.git
cd sotto

cargo build --workspace
cargo test --workspace

Use the CLI locally (no server required):

cargo run -p sotto-cli -- --help
cargo run -p sotto-cli -- init                 # create an identity + project; prints your Emergency Kit
cargo run -p sotto-cli -- set DATABASE_URL     # hidden prompt
cargo run -p sotto-cli -- run -- your-command  # inject secrets as env vars into a subprocess

Secrets are encrypted at rest in a local SQLite store; the master key is cached in the OS keychain with a TTL. Syncing to a server (login/push/pull/setup/share) is optional.

Running the server

The server needs Postgres (a docker compose up -d brings one up for local use):

DATABASE_URL=postgres://sotto:sotto@localhost:5432/sotto cargo run -p sotto-server
curl http://127.0.0.1:8080/health   # → ok

GitHub OAuth login requires GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET. For any non-local deployment also set SOTTO_PUBLIC_URL to the server's externally reachable origin - it builds the GitHub callback URL and must match the OAuth app's registered callback (it otherwise defaults to http://localhost:8080) - and, for the web client, SOTTO_WEB_ORIGIN. Without OAuth the server still boots (serving /health and running migrations), but login and every authenticated endpoint (sync, share creation) are unavailable.

Web client

The browser client runs the same crypto core via WebAssembly (web/):

cd web
npm ci
npm run dev      # dev server (proxies the API to localhost:8080)
npm run build    # production bundle → web/dist (strict CSP + Subresource Integrity)

Deploying

One command brings up a complete hosted instance - Postgres, the server, and Caddy with automatic HTTPS - from deploy/docker-compose.prod.yml; the runbook is deploy/README.md. The pieces also work standalone: serve the web app and API from one origin (so the session cookie and CSP stay same-origin) - the included Caddyfile serves web/dist and reverse-proxies the API, with security headers; Dockerfile builds the server image (migrations run on boot).

Development checks

Run the same core checks used by CI:

cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

Supply-chain policy is defined in deny.toml and checked in CI with cargo-deny:

cargo deny check

The full lockfile audit also runs in the supply-chain CI job. Use Python 3.11 or newer; CI uses Python 3.12. The checker requires cargo-audit exactly 0.22.2:

cargo install cargo-audit --version 0.22.2 --locked
python3 -B -m unittest discover -s scripts/tests -v
scripts/check-cargo-audit

The audit policy records exact exceptions for dormant rsa and spin lock entries. Each must match its package, version, source, kind and finding. It must have no normal, build or dev dependency path across all targets, with default and all workspace features. Cargo's dev edges correspond to dev-dependencies.

New findings, changed identities, reachable packages, failed scans and stale exceptions fail CI. Remove an exception in the same PR that removes its finding; an empty exception list requires a clean audit. The checker exits 0 on success and 1 on policy or scan failure. Raw cargo audit still reports the dormant findings; the exceptions do not fix the vulnerable/yanked releases.

Project audit defaults keep advisory fetching and yanked checks enabled and override a developer's global audit configuration. Do not add advisory ignores or scan filters.

JSON mode can silently skip yanked checks in cargo-audit 0.22.2. The checker therefore requires an independent terminal-format scan with no registry failures and matching finding identities. This establishes a separate successful scan; it does not prove the earlier JSON scan completed.

The cross-implementation gate proves the native and WASM builds agree - native-produced ciphertext decrypts byte-for-byte in WASM from shared golden vectors:

wasm-pack test --node crates/wasm

The web build and its dependency audit run in CI (.github/workflows/ci.yml).

Telemetry

The server sends one anonymous ping per day (the first 10-20 minutes after boot) to https://getsotto.co.uk/telemetry/v1/ping, so we can count active instances and see which versions are in the wild. The response names the latest release, and the server logs a line when it is running an outdated version. This is the entire payload - the sending code is crates/server/src/telemetry.rs, and a unit test pins the payload to exactly these four fields:

{ "instance_id": "0d0972a6-…", "version": "0.2.0", "os": "linux", "arch": "x86_64" }

instance_id is a random UUID generated once and stored in your database - derived from nothing, so it identifies no hardware, host, or account; deleting it makes the instance a fresh anonymous counter. The ingest side stores no IP addresses and no derived location. There are no org, member, or secret counts, and no usage events. The CLI, web client, and WASM never send anything.

Opt out with SOTTO_TELEMETRY=off (or the cross-tool DO_NOT_TRACK=1) - when disabled the task is never started and no request is ever made. SOTTO_TELEMETRY_URL redirects the ping (e.g. to aggregate a private fleet), and records idle for 12 months are purged from the hosted census.

Security

Sotto's model is zero-knowledge: plaintext secrets and usable decryption keys stay on client devices, and the server sees only ciphertext plus minimal metadata. This is implemented but not yet independently audited - see SECURITY.md for the model, the honest metadata exposure, how the (re-fetched, weaker) web surface is hardened, and how to verify signed releases. The full adversary model, guarantees, and explicit non-goals are published in THREAT-MODEL.md. Report vulnerabilities privately per SECURITY.md.

Contributing

Sotto is Apache-2.0 and welcomes contributions. Start at CONTRIBUTING.md; good first issues are labelled, and questions that are not bugs belong in Discussions.

Please report vulnerabilities privately per SECURITY.md.

Licence

Licensed under the Apache License, Version 2.0 - all crates and the web client. You may not use this project except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.

About

end to end encrypted secret sync for developer teams. encrypt locally; the server only ever stores ciphertext.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages