Skip to content

Latest commit

 

History

671 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).

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@543d1af56ac81d1f1511d88c3d269106e8513a28 # merged v1.1 implementation
        with:
          sotto-version: v0.4.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. The example pins the merged v1.1 implementation by full commit SHA because a numbered action release has not yet been published. 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 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 share DATABASE_URL     # one-time, burn-after-reading link for a single secret

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

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.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages