From 399702e452b122df1ee729015dbebe14b7c0c614 Mon Sep 17 00:00:00 2001 From: Maxerns Date: Mon, 26 Oct 2026 20:31:51 +0000 Subject: [PATCH 1/2] pin the linux local walkthrough workflow to cli release 0.9.0 the linux walkthrough job still pinned cli release 0.6.0 and fetched the installer script from an older commit. update the version environment variable and the pinned installer commit to release 0.9.0 so the disposable keyring check exercises the latest published cli binary. Signed-off-by: Maxerns --- .github/workflows/check-examples.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml index b14523f..235af67 100644 --- a/.github/workflows/check-examples.yml +++ b/.github/workflows/check-examples.yml @@ -55,11 +55,11 @@ jobs: python-version: '3.13' - name: Install local keyring and pinned Sotto release env: - SOTTO_VERSION: v0.6.0 + SOTTO_VERSION: v0.9.0 run: | sudo apt-get update -qq sudo apt-get install -y gnome-keyring dbus-x11 - curl -fsSL https://raw.githubusercontent.com/getsotto/sotto/be30886644d321929b371aa56cf0e528c6a8edb9/install.sh | sh + curl -fsSL https://raw.githubusercontent.com/getsotto/sotto/fbd4f2542daf5580da361aabe93e8804758df6b5/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Run the documented flow in a disposable keyring run: | From fafc6062094d25c8a325db96dc7b713e66e9cd1c Mon Sep 17 00:00:00 2001 From: Maxerns Date: Mon, 26 Oct 2026 20:32:21 +0000 Subject: [PATCH 2/2] verify local status, environment copy, dotenv import, and session locking in check sotto expand the disposable linux keyring walkthrough script beyond init, set, and run so it also checks json status output before and after initialisation, default environment creation, json secret listing, environment promotion to staging, local dotenv import, and session locking and unlocking. command output stays withheld on failure so the emergency kit and secret values never reach ci logs. Signed-off-by: Maxerns --- scripts/check_sotto.py | 70 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/scripts/check_sotto.py b/scripts/check_sotto.py index a027cc2..9689268 100644 --- a/scripts/check_sotto.py +++ b/scripts/check_sotto.py @@ -1,5 +1,6 @@ """Exercise the real local Sotto flow on a disposable Linux CI runner only.""" +import json import os from pathlib import Path import subprocess @@ -10,7 +11,7 @@ def main(): - # SOTTO_DATA_DIR does not isolate OS-keychain entries. Never initialise a + # SOTTO_DATA_DIR does not isolate OS keychain entries. Never initialise a # throwaway identity in a developer's real keychain. if sys.platform != "linux" or os.environ.get("GITHUB_ACTIONS") != "true": raise SystemExit("Run this check only on the disposable Linux GitHub Actions runner.") @@ -22,20 +23,79 @@ def main(): env["SOTTO_PASSWORD"] = "disposable-ci-demo-password" def run(command, expected, stdin=None): - result = subprocess.run(command, cwd=folder, env=env, input=stdin, text=True, capture_output=True, timeout=120) + result = subprocess.run( + command, + cwd=folder, + env=env, + input=stdin, + text=True, + capture_output=True, + timeout=120, + ) if result.returncode != expected: - raise SystemExit(f"Unexpected exit status from {command[0]}; output withheld to keep the Emergency Kit private.") + raise SystemExit( + f"Unexpected exit status from {command[:2]}; output withheld to keep the Emergency Kit private." + ) return result + def status(): + return json.loads(run(["sotto", "status", "--json"], 0).stdout) + python = [sys.executable, str(ROOT / "python/main.py")] run(python, 1) + + if status() != {"initialized": False, "unlocked": False, "project": None}: + raise SystemExit("Unexpected initial status before init.") + run(["sotto", "init", "--name", "sotto-example"], 0) + + if status() != { + "initialized": True, + "unlocked": True, + "project": {"name": "sotto-example", "environment": "dev"}, + }: + raise SystemExit("Unexpected status after init.") + + envs_out = run(["sotto", "env", "ls"], 0).stdout + envs = {line.lstrip("* ").strip() for line in envs_out.splitlines() if line.strip()} + if envs != {"dev", "staging", "prod"}: + raise SystemExit("Unexpected environment list after init.") + run(["sotto", "set", "DEMO_SECRET", "--stdin"], 0, "hello-from-sotto") + + if json.loads(run(["sotto", "ls", "--json"], 0).stdout) != ["DEMO_SECRET"]: + raise SystemExit("Unexpected secret list in dev.") + result = run(["sotto", "run", "--", *python], 0) if result.stdout != "Sotto demo: secret loaded successfully.\n": - raise SystemExit("Unexpected demo output (withheld).") + raise SystemExit("Unexpected demo output in dev (withheld).") + + run(["sotto", "env", "copy", "dev", "staging", "--confirm"], 0) + staging_result = run(["sotto", "run", "--env", "staging", "--", *python], 0) + if staging_result.stdout != "Sotto demo: secret loaded successfully.\n": + raise SystemExit("Unexpected demo output in staging (withheld).") + + dotenv_path = Path(folder) / ".env.staging" + dotenv_path.write_text("IMPORTED_KEY=synthetic-dotenv-value\n", encoding="utf-8") + run(["sotto", "import", "--env", "staging", str(dotenv_path)], 0) + if json.loads(run(["sotto", "ls", "--env", "staging", "--json"], 0).stdout) != [ + "DEMO_SECRET", + "IMPORTED_KEY", + ]: + raise SystemExit("Unexpected secret list in staging after dotenv import.") + + run(["sotto", "lock"], 0) + if status()["unlocked"] is not False: + raise SystemExit("Expected session to be locked after sotto lock.") + + run(["sotto", "unlock"], 0) + if status()["unlocked"] is not True: + raise SystemExit("Expected session to be unlocked after sotto unlock.") + run(python, 1) - print("PASS real Sotto: missing -> init -> set -> injected success -> direct run still missing") + print( + "PASS real Sotto: status -> init -> env ls -> set -> ls -> run -> env copy -> import -> lock/unlock -> direct run still missing" + ) if __name__ == "__main__":