From aafd23751369eef8937fcf1f963167ea04a9d536 Mon Sep 17 00:00:00 2001 From: SlZeroth Date: Wed, 1 Jul 2026 21:25:46 +0900 Subject: [PATCH 1/3] fix(security): Ed25519-signed auto-update, replacing pipe-to-shell (CWE-494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The session_start auto-updater fetched the installer and executed it with no integrity check (`curl … | bash`, `iwr … | iex`) — a standing RCE channel: a compromised vibemon.dev or GitHub release would run arbitrary code on every user within 24h. The only validation was a <=16-char length check on the version string. Reported by Sascha Heyer. The updater now downloads the installer + a detached Ed25519 signature, verifies the bytes against a public key baked into the client, and executes ONLY on a valid signature — fail-closed on an invalid/missing signature or an unconfigured (placeholder) key. The signing seed lives ONLY in the VIBEMON_SIGNING_SEED GitHub Actions secret and is never committed. - src/ed25519.py: pure-stdlib Ed25519 verify/sign (RFC 8032 reference) — no new client prerequisite (python3 already required), cross-platform - src/verify_main.py + src/release_pubkey.py: client verifier + baked-in pubkey - scripts/keygen.py / scripts/sign.py: one-time keygen + release-time signing - src/notify.sh, src/notify.py: download → verify → exec (all three unsigned exec paths removed: Unix bash, Windows powershell, python client) - tests/test_signature.py: RFC 8032 KAT, tamper-rejection, placeholder fail-closed, PyNaCl cross-check - SECURITY.md / README.md: document signing; disclose auto-update + telemetry Verified end-to-end on real Linux (Docker): valid release runs, tampered and unsigned artifacts are refused. 178 tests pass; dist reproducible. Ships fail-closed (auto-update stays off until keygen + secret are set). NOTE: the CI workflow changes (release.yml signing step, test.yml pynacl) are a follow-up commit — they need the `workflow` OAuth scope to push. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01He8wXLDfXZSoyjf9wrdo3y --- README.md | 39 +- SECURITY.md | 31 +- dist/install.ps1 | 784 +++++++++++++++++++++------------------- dist/install.ps1.sha256 | 2 +- dist/install.sh | 263 +++++++++++++- dist/install.sh.sha256 | 2 +- scripts/build.py | 13 +- scripts/keygen.py | 64 ++++ scripts/sign.py | 61 ++++ src/ed25519.py | 162 +++++++++ src/notify.py | 68 +++- src/notify.sh | 27 +- src/release_pubkey.py | 17 + src/verify_main.py | 60 +++ tests/test_signature.py | 134 +++++++ 15 files changed, 1336 insertions(+), 391 deletions(-) create mode 100644 scripts/keygen.py create mode 100644 scripts/sign.py create mode 100644 src/ed25519.py create mode 100644 src/release_pubkey.py create mode 100644 src/verify_main.py create mode 100644 tests/test_signature.py diff --git a/README.md b/README.md index b91644c..7c06360 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,17 @@ It bundles an embedded CPython (nothing to install first) and runs the same `install.py` the script path uses — zero install logic of its own (`installer/windows/`). macOS/Linux stay on the one-liner above. +> **Before you install, two things to know:** +> - **Auto-update:** on session start (once/day) VibeMon checks for a new +> release and installs it. Updates are **Ed25519-signed and verified against a +> key baked into the client before running** — a tampered release fails +> verification and is not executed ([SECURITY.md](SECURITY.md)). Pin a tag (the +> second command above) if you'd rather update by hand. +> - **Telemetry:** hooks send an anonymized activity envelope (event type, +> file paths/extensions, line counts, git `owner/repo`, local hour/timezone). +> **Code, prompts, diffs, and command bodies are stripped** and this is +> enforced in CI. Full details + opt-outs: [PRIVACY.md](PRIVACY.md). + --- ## What this code does @@ -150,16 +161,28 @@ git diff contract/golden/ # REVIEW carefully ## Releasing +**One-time setup (enables signed auto-update):** run `python3 scripts/keygen.py`, +store the printed seed as the GitHub Actions secret `VIBEMON_SIGNING_SEED`, and +commit the updated `src/release_pubkey.py`. Until this is done the shipped client +is fail-closed: it will not auto-update (no unsigned exec), and releases fail at +the signing step. + 1. Edit `VERSION` (e.g. `10` → `11`). -2. Run `python3 scripts/build.py`. Commit `dist/install.sh` + `VERSION`. +2. Run `python3 scripts/build.py`. Commit `dist/` + `VERSION`. 3. Tag: `git tag v11 && git push --tags`. -4. CI builds, tests, attaches `install.sh` + `sha256sum.txt` to a - GitHub Release. -5. `vibemon.dev/install.sh` automatically redirects to the latest tag. - -The `auto-update` mechanism inside `notify.sh` polls the new release -once a day on `session_start` and re-runs `install.sh` when VERSION -bumps. +4. CI builds, tests, **signs** (`scripts/sign.py` → `install.sh.sig` / + `install.ps1.sig`), and attaches the installers + `.sig` + `sha256sum.txt` + to a GitHub Release. +5. `vibemon.dev/install.sh` (and `…/install.sh.sig`) redirect to the latest tag. + +The `auto-update` mechanism inside `notify.sh` / `notify.py` polls the new release +once a day on `session_start`, **downloads the installer + its signature, verifies +it against the baked-in public key, and only then** re-runs `install.sh` when +VERSION bumps. A tampered or unsigned artifact is never executed. + +> **Serving note:** `vibemon.dev` must serve/redirect `install.sh.sig` and +> `install.ps1.sig` alongside the installers (same 302-to-Release pattern). If the +> `.sig` is missing the client fails closed and skips the update. --- diff --git a/SECURITY.md b/SECURITY.md index 2b9ba72..16d9cf2 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,6 +10,28 @@ is supported. Older `dist/install.sh` artifacts are kept on GitHub Releases for audit purposes only — the auto-update mechanism in `notify.sh` will roll users forward within 24 hours of a new release. +## How the auto-updater is secured (signed releases) + +`notify.sh` / `notify.py` check for a new version once/day on session start. The +updater **never pipes a remote script to a shell.** It downloads the installer +**and a detached Ed25519 signature** (`install.sh.sig` / `install.ps1.sig`), then +verifies the bytes against a **public key baked into the client** +(`src/release_pubkey.py`) with a pure-stdlib verifier (`src/ed25519.py`) before +executing anything. Invalid signature, missing signature, or an unconfigured key +⇒ **nothing runs** (fail-closed). + +The signing **secret seed** exists only as the GitHub Actions secret +`VIBEMON_SIGNING_SEED` and is never in the repo. A compromised `vibemon.dev` or +GitHub Release can swap the artifact bytes but **cannot forge a signature**, so a +tampered installer fails verification and is not executed. This closes the prior +unsigned-update channel (CWE-494). Correctness + fail-closed behavior are enforced +in CI by `tests/test_signature.py` (RFC 8032 known-answer vector, tamper +rejection, and a PyNaCl cross-check). + +Key rotation: run `python3 scripts/keygen.py`, store the printed seed as the +`VIBEMON_SIGNING_SEED` secret, commit the updated `src/release_pubkey.py` + `dist/`, +and cut a release. + ## Reporting a vulnerability **Do not file a public issue or pull request.** @@ -45,11 +67,14 @@ We design and test against: install URL is a 302 redirect to a GitHub Release artifact, which is immutable and matches the build for that tag. +- **Distribution tampering / compromised release** — a malicious installer + served from `vibemon.dev` or a GitHub Release (including via a compromised + account or domain). Mitigation: **Ed25519-signed releases** verified by the + client before execution (see "How the auto-updater is secured" above). Without + the offline signing seed, a swapped artifact fails verification and never runs. + We do **not** currently defend against: -- A compromised GitHub account pushing a malicious release. Releases - are not yet signed (`cosign` integration is a planned follow-up). - Pin to a known-good tag if you need stronger guarantees. - A malicious AI agent manipulating the JSON it sends to `notify.sh` in a way that triggers unintended POSTs to the VibeMon backend. The envelope is sent regardless of payload — no data exfiltration vector diff --git a/dist/install.ps1 b/dist/install.ps1 index 4b5543f..fa9f168 100644 --- a/dist/install.ps1 +++ b/dist/install.ps1 @@ -32,370 +32,426 @@ $VIBEMON_VERSION = "25" # install.py, merge_*.py # Format: gzip-compressed tar, base64-encoded. Reproducible (mtime=0). $VIBEMON_BUNDLE_B64 = @' -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 +c/u5/dx+bj+3n5/v8/8DQ0ZoEwCQAQA= '@ function Invoke-VibeMonInstall { diff --git a/dist/install.ps1.sha256 b/dist/install.ps1.sha256 index 99e54b5..e614b36 100644 --- a/dist/install.ps1.sha256 +++ b/dist/install.ps1.sha256 @@ -1 +1 @@ -42272adf9950f88274cad05578960b1a012bb1f1027617800520798a78dac25f install.ps1 +e277ec2287a6f83e9893c579ec8672a4305b9ed569d02a257e9ce6f2560f6bc5 install.ps1 diff --git a/dist/install.sh b/dist/install.sh index 673bcc9..c791f13 100755 --- a/dist/install.sh +++ b/dist/install.sh @@ -204,8 +204,269 @@ if [ "$EVENT_TYPE" = "session_start" ]; then local CURRENT="" [ -f "$VIBEMON_DIR/version" ] && CURRENT=$(cat "$VIBEMON_DIR/version") # Sanity: LATEST must be a short numeric/version-ish string, not an HTML body. + # (The version string is unauthenticated, but it ONLY decides *whether* to + # attempt an update — the actual code never runs unless the downloaded + # installer passes Ed25519 signature verification below.) if [ -n "$LATEST" ] && [ ${#LATEST} -le 16 ] && [ "$LATEST" != "$CURRENT" ]; then - curl -fsSL "https://vibemon.dev/install.sh" 2>/dev/null | bash -s 2>/dev/null + # SIGNED update (CWE-494 fix): NEVER pipe a remote script to bash. Download + # the installer + its detached signature to a temp dir, verify the signature + # against the baked-in release public key with a pure-stdlib Ed25519 verifier, + # and execute ONLY on a valid signature. A compromised vibemon.dev / GitHub + # release can swap the bytes but cannot forge a signature without the secret + # seed (which lives only in a GitHub Actions secret), so verification fails + # and nothing runs. Unconfigured/placeholder key => verifier returns non-zero + # => no execution (fail-closed). The `if python3 … </dev/null) || return 0 + if curl -fsSL "https://vibemon.dev/install.sh" -o "$TMPD/install.sh" 2>/dev/null \ + && curl -fsSL "https://vibemon.dev/install.sh.sig" -o "$TMPD/install.sh.sig" 2>/dev/null; then + if python3 - "$TMPD/install.sh" "$TMPD/install.sh.sig" >/dev/null 2>&1 <<'VIBEMON_VERIFY' +""" +ed25519.py — self-contained, stdlib-only Ed25519 (RFC 8032). + +Why this exists: the auto-updater must verify a signature over the installer +BEFORE executing it, and the client environment is guaranteed to have `python3` +(install.sh checks for it; notify.py *is* python3) but NOTHING else — no pip +packages, no `minisign`/`openssl` binary, cross-platform (macOS/Linux/Windows). +So verification is done here in pure Python on stdlib `hashlib.sha512` only. + +This is the well-known public-domain reference implementation from the Ed25519 +authors (D. J. Bernstein et al.) / RFC 8032 Appendix A, unmodified in its math. +It is deliberately the simple (slow) reference, not an optimized one: the client +verifies a single signature at most once per day, so clarity/auditability beats +speed, and matching the reference exactly is what lets the CI test check it +against RFC 8032 test vectors + an independent library (PyNaCl). + +Used by: + - src/verify.py — the client-side verifier CLI (embedded into notify.sh / + shipped in the Windows bundle). + - scripts/sign.py / scripts/keygen.py — release-time signing (CI) + one-time + key generation. Signing uses the SAME code path so the CI round-trip test + (`sign` here → `verify` here → PyNaCl) proves the whole chain. + +SECURITY: `checkvalid()` RAISES on any failure (bad signature, wrong length, +off-curve point). Callers MUST treat any exception as "reject" (fail-closed). +""" + +import hashlib + +b = 256 +q = 2 ** 255 - 19 +L = 2 ** 252 + 27742317777372353535851937790883648493 + + +def _H(m): + return hashlib.sha512(m).digest() + + +def _expmod(base, e, m): + if e == 0: + return 1 + t = _expmod(base, e // 2, m) ** 2 % m + if e & 1: + t = (t * base) % m + return t + + +def _inv(x): + return _expmod(x, q - 2, q) + + +d = -121665 * _inv(121666) % q +_I = _expmod(2, (q - 1) // 4, q) + + +def _xrecover(y): + xx = (y * y - 1) * _inv(d * y * y + 1) + x = _expmod(xx, (q + 3) // 8, q) + if (x * x - xx) % q != 0: + x = (x * _I) % q + if x % 2 != 0: + x = q - x + return x + + +_By = 4 * _inv(5) % q +_Bx = _xrecover(_By) +B = [_Bx % q, _By % q] + + +def _edwards(P, Q): + x1, y1 = P + x2, y2 = Q + x3 = (x1 * y2 + x2 * y1) * _inv(1 + d * x1 * x2 * y1 * y2) + y3 = (y1 * y2 + x1 * x2) * _inv(1 - d * x1 * x2 * y1 * y2) + return [x3 % q, y3 % q] + + +def _scalarmult(P, e): + if e == 0: + return [0, 1] + Q = _scalarmult(P, e // 2) + Q = _edwards(Q, Q) + if e & 1: + Q = _edwards(Q, P) + return Q + + +def _encodeint(y): + bits = [(y >> i) & 1 for i in range(b)] + return bytes(sum(bits[i * 8 + j] << j for j in range(8)) for i in range(b // 8)) + + +def _encodepoint(P): + x, y = P + bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] + return bytes(sum(bits[i * 8 + j] << j for j in range(8)) for i in range(b // 8)) + + +def _bit(h, i): + return (h[i // 8] >> (i % 8)) & 1 + + +def _Hint(m): + h = _H(m) + return sum(2 ** i * _bit(h, i) for i in range(2 * b)) + + +def publickey(seed): + """32-byte secret seed -> 32-byte public key.""" + if len(seed) != 32: + raise ValueError("seed must be 32 bytes") + h = _H(seed) + a = 2 ** (b - 2) + sum(2 ** i * _bit(h, i) for i in range(3, b - 2)) + A = _scalarmult(B, a) + return _encodepoint(A) + + +def signature(m, seed, pk): + """Sign message bytes `m` with 32-byte seed + its 32-byte public key -> 64-byte sig.""" + h = _H(seed) + a = 2 ** (b - 2) + sum(2 ** i * _bit(h, i) for i in range(3, b - 2)) + r = _Hint(bytes(h[i] for i in range(b // 8, b // 4)) + m) + R = _scalarmult(B, r) + S = (r + _Hint(_encodepoint(R) + pk + m) * a) % L + return _encodepoint(R) + _encodeint(S) + + +def _isoncurve(P): + x, y = P + return (-x * x + y * y - 1 - d * x * x * y * y) % q == 0 + + +def _decodeint(s): + return sum(2 ** i * _bit(s, i) for i in range(0, b)) + + +def _decodepoint(s): + y = sum(2 ** i * _bit(s, i) for i in range(0, b - 1)) + x = _xrecover(y) + if x & 1 != _bit(s, b - 1): + x = q - x + P = [x, y] + if not _isoncurve(P): + raise ValueError("decoding point that is not on curve") + return P + + +def checkvalid(sig, m, pk): + """Verify 64-byte `sig` over message `m` under 32-byte public key `pk`. + RAISES ValueError on ANY failure — callers MUST treat that as reject.""" + if len(sig) != b // 4: + raise ValueError("signature length is wrong") + if len(pk) != b // 8: + raise ValueError("public-key length is wrong") + R = _decodepoint(sig[0:b // 8]) + A = _decodepoint(pk) + S = _decodeint(sig[b // 8:b // 4]) + h = _Hint(_encodepoint(R) + pk + m) + if _scalarmult(B, S) != _edwards(R, _scalarmult(A, h)): + raise ValueError("signature does not pass verification") + return True +""" +release_pubkey.py — the ONE source of truth for VibeMon's release-signing +public key. The matching SECRET seed lives ONLY in the GitHub Actions secret +`VIBEMON_SIGNING_SEED` and is NEVER committed here. + +The auto-updater (notify.sh / notify.py) verifies every downloaded installer +against this key before executing it. Rotate with `python3 scripts/keygen.py`, +which overwrites the value below and prints a new secret seed to store in the +GitHub secret. + +FAIL-CLOSED: while this is the 64-hex-char placeholder of all zeros (i.e. no +real key has been generated yet), signature verification cannot succeed, so the +auto-updater refuses to run ANY downloaded installer. That is the safe default: +no signed key => no auto-update, never an unsigned exec. Run keygen to activate. +""" + +RELEASE_PUBKEY_HEX = "0000000000000000000000000000000000000000000000000000000000000000" +""" +verify_main.py — client-side installer verifier (used by notify.sh's auto-update). + +Invoked as `python3 - ` with ed25519.py + release_pubkey.py +concatenated ABOVE it (see scripts/build.py's notify.sh embed). Exit codes: + 0 signature valid -> caller may execute the installer + 1 signature INVALID -> caller MUST NOT execute (tampered / wrong key) + 2 usage/IO error -> caller MUST NOT execute + 3 no release key configured (placeholder) -> caller MUST NOT execute +Anything non-zero => do not run. Fail-closed by construction. + +Also importable/standalone for tests: falls back to real imports when the +symbols aren't already in scope from the embed. +""" + +import base64 +import sys + +try: + checkvalid # provided by the embedded ed25519.py +except NameError: # standalone / test import + from ed25519 import checkvalid + +try: + RELEASE_PUBKEY_HEX # provided by the embedded release_pubkey.py +except NameError: + from release_pubkey import RELEASE_PUBKEY_HEX + + +def verify_file(installer_path, sig_path, pubkey_hex): + pubkey_hex = (pubkey_hex or "").strip() + try: + pk = bytes.fromhex(pubkey_hex) + except ValueError: + return 3 + # Placeholder / unconfigured key (all-zero or wrong length) => fail-closed. + if len(pk) != 32 or pk == b"\x00" * 32: + return 3 + try: + with open(installer_path, "rb") as f: + data = f.read() + with open(sig_path, "rb") as f: + sig = base64.b64decode(f.read().strip()) + except (OSError, ValueError): + return 2 + try: + checkvalid(sig, data, pk) + except Exception: # noqa: BLE001 — ANY failure means reject + return 1 + return 0 + + +def main(argv): + if len(argv) != 3: + return 2 + return verify_file(argv[1], argv[2], RELEASE_PUBKEY_HEX) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) +VIBEMON_VERIFY + then + bash "$TMPD/install.sh" 2>/dev/null + fi + fi + rm -rf "$TMPD" 2>/dev/null || true fi } (_vibemon_update_check /dev/null 2>&1) & disown 2>/dev/null || true diff --git a/dist/install.sh.sha256 b/dist/install.sh.sha256 index 52e59f9..497af78 100644 --- a/dist/install.sh.sha256 +++ b/dist/install.sh.sha256 @@ -1 +1 @@ -ca34c5f284f3608970bdbb2c0493600a8be8a692d4e2fa742b65b82e627f3d3b install.sh +df301e26f0d87e91dca13063c62ed6de634a7c03e5c8aee12afd38f3bf82f585 install.sh diff --git a/scripts/build.py b/scripts/build.py index 9ff1396..b2338d1 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -41,6 +41,8 @@ "lock.py", "classify.py", "extract.py", + "ed25519.py", + "release_pubkey.py", "notify.py", "install.py", "merge_claude.py", @@ -84,11 +86,16 @@ def _read_version(): # ─── Unix install.sh ────────────────────────────────────────────────── def build_notify_sh(): - """Build the standalone notify.sh — embeds classify.py + extract.py.""" + """Build the standalone notify.sh — embeds the envelope builder + (classify.py + extract.py) and the signed-update verifier + (ed25519.py + release_pubkey.py + verify_main.py).""" template = read("notify.sh") providers = { - "classify.py": lambda: read("classify.py").rstrip("\n"), - "extract.py": lambda: read("extract.py").rstrip("\n"), + "classify.py": lambda: read("classify.py").rstrip("\n"), + "extract.py": lambda: read("extract.py").rstrip("\n"), + "ed25519.py": lambda: read("ed25519.py").rstrip("\n"), + "release_pubkey.py": lambda: read("release_pubkey.py").rstrip("\n"), + "verify_main.py": lambda: read("verify_main.py").rstrip("\n"), } return substitute(template, providers) diff --git a/scripts/keygen.py b/scripts/keygen.py new file mode 100644 index 0000000..5066fd9 --- /dev/null +++ b/scripts/keygen.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +""" +keygen.py — generate (or rotate) the VibeMon release-signing keypair. + +Run this ONCE to activate signed auto-updates: + + python3 scripts/keygen.py + +It: + 1. generates a fresh 32-byte Ed25519 secret seed (os.urandom), + 2. writes the PUBLIC key into src/release_pubkey.py (commit this change), + 3. prints the SECRET seed (hex) exactly once. + +Store the printed secret as the GitHub Actions repository secret named +`VIBEMON_SIGNING_SEED` (Settings -> Secrets and variables -> Actions). +NEVER commit it, paste it in chat, or store it anywhere else. If it leaks, +run this script again to rotate and cut a new release. + +Rotating invalidates old signatures, which is fine: each new release is signed +with the current key and clients verify against the current committed pubkey. +""" + +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) +import ed25519 # noqa: E402 + +PUBKEY_FILE = os.path.join(ROOT, "src", "release_pubkey.py") + + +def main(): + seed = os.urandom(32) + pub = ed25519.publickey(seed) + + with open(PUBKEY_FILE, encoding="utf-8") as f: + content = f.read() + new = re.sub( + r'RELEASE_PUBKEY_HEX = "[0-9a-fA-F]*"', + f'RELEASE_PUBKEY_HEX = "{pub.hex()}"', + content, + ) + if new == content: + print("ERROR: could not find RELEASE_PUBKEY_HEX assignment to update", file=sys.stderr) + return 1 + with open(PUBKEY_FILE, "w", encoding="utf-8", newline="\n") as f: + f.write(new) + + print("=" * 70) + print("VibeMon release key generated.") + print("=" * 70) + print(f"\nPUBLIC key written to src/release_pubkey.py (COMMIT this):\n {pub.hex()}") + print("\nSECRET seed — store as GitHub Actions secret VIBEMON_SIGNING_SEED,") + print("then clear your terminal. Shown ONCE, never recoverable:\n") + print(f" {seed.hex()}\n") + print("Next: rebuild (python3 scripts/build.py), commit release_pubkey.py") + print("+ dist/, then tag a release so CI signs it.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/sign.py b/scripts/sign.py new file mode 100644 index 0000000..20416b4 --- /dev/null +++ b/scripts/sign.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +sign.py — sign the built dist artifacts (run by CI at release time). + +Reads the secret seed from env `VIBEMON_SIGNING_SEED` (hex, 64 chars), signs +each artifact's raw bytes with Ed25519, and writes `.sig` containing +the base64 of the 64-byte signature. Also self-checks that the signature +verifies against the committed public key in src/release_pubkey.py, so a +key/secret mismatch fails the release instead of shipping a bad signature. + + VIBEMON_SIGNING_SEED= python3 scripts/sign.py + +Signs: dist/install.sh, dist/install.ps1 (extend ARTIFACTS as needed). +""" + +import base64 +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) +import ed25519 # noqa: E402 +from release_pubkey import RELEASE_PUBKEY_HEX # noqa: E402 + +ARTIFACTS = ["dist/install.sh", "dist/install.ps1"] + + +def main(): + seed_hex = os.environ.get("VIBEMON_SIGNING_SEED", "").strip() + try: + seed = bytes.fromhex(seed_hex) + except ValueError: + seed = b"" + if len(seed) != 32: + print("ERROR: VIBEMON_SIGNING_SEED must be 64 hex chars (32 bytes)", file=sys.stderr) + return 1 + + pub = ed25519.publickey(seed) + committed = bytes.fromhex(RELEASE_PUBKEY_HEX.strip()) + if pub != committed: + print("ERROR: secret seed does not match committed src/release_pubkey.py.\n" + " Run scripts/keygen.py and update the secret + pubkey together.", + file=sys.stderr) + return 1 + + for rel in ARTIFACTS: + path = os.path.join(ROOT, rel) + with open(path, "rb") as f: + data = f.read() + sig = ed25519.signature(data, seed, pub) + # Self-check: the signature we just produced MUST verify. Fail-closed. + ed25519.checkvalid(sig, data, pub) + sig_path = path + ".sig" + with open(sig_path, "w", encoding="utf-8", newline="\n") as f: + f.write(base64.b64encode(sig).decode("ascii") + "\n") + print(f"signed {rel} -> {rel}.sig") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/ed25519.py b/src/ed25519.py new file mode 100644 index 0000000..36b2c7a --- /dev/null +++ b/src/ed25519.py @@ -0,0 +1,162 @@ +""" +ed25519.py — self-contained, stdlib-only Ed25519 (RFC 8032). + +Why this exists: the auto-updater must verify a signature over the installer +BEFORE executing it, and the client environment is guaranteed to have `python3` +(install.sh checks for it; notify.py *is* python3) but NOTHING else — no pip +packages, no `minisign`/`openssl` binary, cross-platform (macOS/Linux/Windows). +So verification is done here in pure Python on stdlib `hashlib.sha512` only. + +This is the well-known public-domain reference implementation from the Ed25519 +authors (D. J. Bernstein et al.) / RFC 8032 Appendix A, unmodified in its math. +It is deliberately the simple (slow) reference, not an optimized one: the client +verifies a single signature at most once per day, so clarity/auditability beats +speed, and matching the reference exactly is what lets the CI test check it +against RFC 8032 test vectors + an independent library (PyNaCl). + +Used by: + - src/verify.py — the client-side verifier CLI (embedded into notify.sh / + shipped in the Windows bundle). + - scripts/sign.py / scripts/keygen.py — release-time signing (CI) + one-time + key generation. Signing uses the SAME code path so the CI round-trip test + (`sign` here → `verify` here → PyNaCl) proves the whole chain. + +SECURITY: `checkvalid()` RAISES on any failure (bad signature, wrong length, +off-curve point). Callers MUST treat any exception as "reject" (fail-closed). +""" + +import hashlib + +b = 256 +q = 2 ** 255 - 19 +L = 2 ** 252 + 27742317777372353535851937790883648493 + + +def _H(m): + return hashlib.sha512(m).digest() + + +def _expmod(base, e, m): + if e == 0: + return 1 + t = _expmod(base, e // 2, m) ** 2 % m + if e & 1: + t = (t * base) % m + return t + + +def _inv(x): + return _expmod(x, q - 2, q) + + +d = -121665 * _inv(121666) % q +_I = _expmod(2, (q - 1) // 4, q) + + +def _xrecover(y): + xx = (y * y - 1) * _inv(d * y * y + 1) + x = _expmod(xx, (q + 3) // 8, q) + if (x * x - xx) % q != 0: + x = (x * _I) % q + if x % 2 != 0: + x = q - x + return x + + +_By = 4 * _inv(5) % q +_Bx = _xrecover(_By) +B = [_Bx % q, _By % q] + + +def _edwards(P, Q): + x1, y1 = P + x2, y2 = Q + x3 = (x1 * y2 + x2 * y1) * _inv(1 + d * x1 * x2 * y1 * y2) + y3 = (y1 * y2 + x1 * x2) * _inv(1 - d * x1 * x2 * y1 * y2) + return [x3 % q, y3 % q] + + +def _scalarmult(P, e): + if e == 0: + return [0, 1] + Q = _scalarmult(P, e // 2) + Q = _edwards(Q, Q) + if e & 1: + Q = _edwards(Q, P) + return Q + + +def _encodeint(y): + bits = [(y >> i) & 1 for i in range(b)] + return bytes(sum(bits[i * 8 + j] << j for j in range(8)) for i in range(b // 8)) + + +def _encodepoint(P): + x, y = P + bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] + return bytes(sum(bits[i * 8 + j] << j for j in range(8)) for i in range(b // 8)) + + +def _bit(h, i): + return (h[i // 8] >> (i % 8)) & 1 + + +def _Hint(m): + h = _H(m) + return sum(2 ** i * _bit(h, i) for i in range(2 * b)) + + +def publickey(seed): + """32-byte secret seed -> 32-byte public key.""" + if len(seed) != 32: + raise ValueError("seed must be 32 bytes") + h = _H(seed) + a = 2 ** (b - 2) + sum(2 ** i * _bit(h, i) for i in range(3, b - 2)) + A = _scalarmult(B, a) + return _encodepoint(A) + + +def signature(m, seed, pk): + """Sign message bytes `m` with 32-byte seed + its 32-byte public key -> 64-byte sig.""" + h = _H(seed) + a = 2 ** (b - 2) + sum(2 ** i * _bit(h, i) for i in range(3, b - 2)) + r = _Hint(bytes(h[i] for i in range(b // 8, b // 4)) + m) + R = _scalarmult(B, r) + S = (r + _Hint(_encodepoint(R) + pk + m) * a) % L + return _encodepoint(R) + _encodeint(S) + + +def _isoncurve(P): + x, y = P + return (-x * x + y * y - 1 - d * x * x * y * y) % q == 0 + + +def _decodeint(s): + return sum(2 ** i * _bit(s, i) for i in range(0, b)) + + +def _decodepoint(s): + y = sum(2 ** i * _bit(s, i) for i in range(0, b - 1)) + x = _xrecover(y) + if x & 1 != _bit(s, b - 1): + x = q - x + P = [x, y] + if not _isoncurve(P): + raise ValueError("decoding point that is not on curve") + return P + + +def checkvalid(sig, m, pk): + """Verify 64-byte `sig` over message `m` under 32-byte public key `pk`. + RAISES ValueError on ANY failure — callers MUST treat that as reject.""" + if len(sig) != b // 4: + raise ValueError("signature length is wrong") + if len(pk) != b // 8: + raise ValueError("public-key length is wrong") + R = _decodepoint(sig[0:b // 8]) + A = _decodepoint(pk) + S = _decodeint(sig[b // 8:b // 4]) + h = _Hint(_encodepoint(R) + pk + m) + if _scalarmult(B, S) != _edwards(R, _scalarmult(A, h)): + raise ValueError("signature does not pass verification") + return True diff --git a/src/notify.py b/src/notify.py index 41743ce..de59877 100644 --- a/src/notify.py +++ b/src/notify.py @@ -16,9 +16,11 @@ Stdlib only. """ +import base64 import datetime import json import os +import shutil import subprocess import sys import tempfile @@ -35,6 +37,8 @@ sys.path.insert(0, _HERE) from extract import build_envelope # noqa: E402 +from ed25519 import checkvalid # noqa: E402 +from release_pubkey import RELEASE_PUBKEY_HEX # noqa: E402 # Public Supabase functions URL — same hardcoded constant as notify.sh. API_URL = "https://sirpdtcwawcidhgtltps.supabase.co/functions/v1" @@ -113,6 +117,19 @@ def _detect_project_root(): # ─── Auto-update (session_start, mkdir-locked, daily) ─────────────── +def _download_to(url, dest, timeout=30): + """Download `url` to `dest`. Returns True on success, False on any failure.""" + try: + req = urllib.request.Request(url, headers={"User-Agent": "vibemon-notify"}) + with urllib.request.urlopen(req, timeout=timeout) as r: + data = r.read() + with open(dest, "wb") as f: + f.write(data) + return True + except Exception: + return False + + def _auto_update_once(): """Atomic mkdir-based lock — directory creation is atomic on POSIX and NTFS. Identical semantics to notify.sh's `mkdir "$LOCK_DIR"`. @@ -170,20 +187,53 @@ def _auto_update_once(): if latest == current: return - # Spawn the appropriate self-updater detached so the parent hook - # returns immediately. PowerShell on Windows, bash on Unix. + # SIGNED update (CWE-494 fix): NEVER pipe a remote script to an + # interpreter (`iwr | iex` / `curl | bash`). Download the installer + + # its detached Ed25519 signature, verify the bytes against the baked-in + # release public key, and execute ONLY on a valid signature. A + # compromised vibemon.dev / GitHub release can swap the bytes but cannot + # forge a signature without the secret seed (which lives only in a + # GitHub Actions secret). We're already in the detached __update_check + # helper, so a synchronous download+verify+run here is off the hot path. + try: + pk = bytes.fromhex((RELEASE_PUBKEY_HEX or "").strip()) + except ValueError: + pk = b"" + # Placeholder / unconfigured key => refuse to auto-update (fail-closed). + if len(pk) != 32 or pk == b"\x00" * 32: + return + art = "install.ps1" if IS_WINDOWS else "install.sh" try: + tmpd = tempfile.mkdtemp(prefix="vbm_upd_") + except OSError: + return + try: + inst = os.path.join(tmpd, art) + sig_path = os.path.join(tmpd, art + ".sig") + if not (_download_to("https://vibemon.dev/" + art, inst) + and _download_to("https://vibemon.dev/" + art + ".sig", sig_path)): + return + with open(inst, "rb") as f: + data = f.read() + with open(sig_path, "rb") as f: + sig = base64.b64decode(f.read().strip()) + try: + checkvalid(sig, data, pk) # raises on ANY failure + except Exception: + return # signature invalid -> refuse to run the installer if IS_WINDOWS: - cmd = [ - "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", - "-Command", - "iwr -useb https://vibemon.dev/install.ps1 | iex", - ] + cmd = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", + "-File", inst] else: - cmd = ["bash", "-c", "curl -fsSL https://vibemon.dev/install.sh | bash"] - _spawn_detached(cmd, payload=None) + cmd = ["bash", inst] + # Run synchronously (we're already detached) so cleanup can't race + # the child reading its own installer file. + subprocess.run(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, timeout=180) except Exception: pass + finally: + shutil.rmtree(tmpd, ignore_errors=True) finally: try: os.rmdir(lock_dir) diff --git a/src/notify.sh b/src/notify.sh index 3c1f70d..9153690 100644 --- a/src/notify.sh +++ b/src/notify.sh @@ -91,8 +91,33 @@ if [ "$EVENT_TYPE" = "session_start" ]; then local CURRENT="" [ -f "$VIBEMON_DIR/version" ] && CURRENT=$(cat "$VIBEMON_DIR/version") # Sanity: LATEST must be a short numeric/version-ish string, not an HTML body. + # (The version string is unauthenticated, but it ONLY decides *whether* to + # attempt an update — the actual code never runs unless the downloaded + # installer passes Ed25519 signature verification below.) if [ -n "$LATEST" ] && [ ${#LATEST} -le 16 ] && [ "$LATEST" != "$CURRENT" ]; then - curl -fsSL "https://vibemon.dev/install.sh" 2>/dev/null | bash -s 2>/dev/null + # SIGNED update (CWE-494 fix): NEVER pipe a remote script to bash. Download + # the installer + its detached signature to a temp dir, verify the signature + # against the baked-in release public key with a pure-stdlib Ed25519 verifier, + # and execute ONLY on a valid signature. A compromised vibemon.dev / GitHub + # release can swap the bytes but cannot forge a signature without the secret + # seed (which lives only in a GitHub Actions secret), so verification fails + # and nothing runs. Unconfigured/placeholder key => verifier returns non-zero + # => no execution (fail-closed). The `if python3 … </dev/null) || return 0 + if curl -fsSL "https://vibemon.dev/install.sh" -o "$TMPD/install.sh" 2>/dev/null \ + && curl -fsSL "https://vibemon.dev/install.sh.sig" -o "$TMPD/install.sh.sig" 2>/dev/null; then + if python3 - "$TMPD/install.sh" "$TMPD/install.sh.sig" >/dev/null 2>&1 <<'VIBEMON_VERIFY' +# %%EMBED:ed25519.py%% +# %%EMBED:release_pubkey.py%% +# %%EMBED:verify_main.py%% +VIBEMON_VERIFY + then + bash "$TMPD/install.sh" 2>/dev/null + fi + fi + rm -rf "$TMPD" 2>/dev/null || true fi } (_vibemon_update_check /dev/null 2>&1) & disown 2>/dev/null || true diff --git a/src/release_pubkey.py b/src/release_pubkey.py new file mode 100644 index 0000000..6a29842 --- /dev/null +++ b/src/release_pubkey.py @@ -0,0 +1,17 @@ +""" +release_pubkey.py — the ONE source of truth for VibeMon's release-signing +public key. The matching SECRET seed lives ONLY in the GitHub Actions secret +`VIBEMON_SIGNING_SEED` and is NEVER committed here. + +The auto-updater (notify.sh / notify.py) verifies every downloaded installer +against this key before executing it. Rotate with `python3 scripts/keygen.py`, +which overwrites the value below and prints a new secret seed to store in the +GitHub secret. + +FAIL-CLOSED: while this is the 64-hex-char placeholder of all zeros (i.e. no +real key has been generated yet), signature verification cannot succeed, so the +auto-updater refuses to run ANY downloaded installer. That is the safe default: +no signed key => no auto-update, never an unsigned exec. Run keygen to activate. +""" + +RELEASE_PUBKEY_HEX = "0000000000000000000000000000000000000000000000000000000000000000" diff --git a/src/verify_main.py b/src/verify_main.py new file mode 100644 index 0000000..65a6ae2 --- /dev/null +++ b/src/verify_main.py @@ -0,0 +1,60 @@ +""" +verify_main.py — client-side installer verifier (used by notify.sh's auto-update). + +Invoked as `python3 - ` with ed25519.py + release_pubkey.py +concatenated ABOVE it (see scripts/build.py's notify.sh embed). Exit codes: + 0 signature valid -> caller may execute the installer + 1 signature INVALID -> caller MUST NOT execute (tampered / wrong key) + 2 usage/IO error -> caller MUST NOT execute + 3 no release key configured (placeholder) -> caller MUST NOT execute +Anything non-zero => do not run. Fail-closed by construction. + +Also importable/standalone for tests: falls back to real imports when the +symbols aren't already in scope from the embed. +""" + +import base64 +import sys + +try: + checkvalid # provided by the embedded ed25519.py +except NameError: # standalone / test import + from ed25519 import checkvalid + +try: + RELEASE_PUBKEY_HEX # provided by the embedded release_pubkey.py +except NameError: + from release_pubkey import RELEASE_PUBKEY_HEX + + +def verify_file(installer_path, sig_path, pubkey_hex): + pubkey_hex = (pubkey_hex or "").strip() + try: + pk = bytes.fromhex(pubkey_hex) + except ValueError: + return 3 + # Placeholder / unconfigured key (all-zero or wrong length) => fail-closed. + if len(pk) != 32 or pk == b"\x00" * 32: + return 3 + try: + with open(installer_path, "rb") as f: + data = f.read() + with open(sig_path, "rb") as f: + sig = base64.b64decode(f.read().strip()) + except (OSError, ValueError): + return 2 + try: + checkvalid(sig, data, pk) + except Exception: # noqa: BLE001 — ANY failure means reject + return 1 + return 0 + + +def main(argv): + if len(argv) != 3: + return 2 + return verify_file(argv[1], argv[2], RELEASE_PUBKEY_HEX) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tests/test_signature.py b/tests/test_signature.py new file mode 100644 index 0000000..b7deafc --- /dev/null +++ b/tests/test_signature.py @@ -0,0 +1,134 @@ +""" +test_signature.py — proves the signed-auto-update chain is correct AND +fail-closed. This is the security-critical test: a bug that makes verification +wrongly PASS would silently reopen the RCE channel, so we assert both that valid +signatures are accepted and that every tampering is rejected. + +Covers: + 1. RFC 8032 known-answer vector (our Ed25519 math matches the standard). + 2. Round-trip: keygen -> sign -> verify passes. + 3. Fail-closed: tampered file / wrong key / flipped-bit sig / truncated sig / + placeholder (unconfigured) key all make verify_main.verify_file return + non-zero (i.e. "do not execute"). + 4. Cross-check against PyNaCl when it is importable (CI installs it). +""" + +import base64 +import binascii +import os + +import ed25519 +import verify_main + + +def _sig_file(tmp_path, sig_bytes): + p = tmp_path / "install.sh.sig" + p.write_text(base64.b64encode(sig_bytes).decode("ascii") + "\n") + return str(p) + + +def test_rfc8032_known_answer(): + # RFC 8032 Section 7.1, TEST 2. + seed = binascii.unhexlify( + "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb") + msg = binascii.unhexlify("72") + assert ed25519.publickey(seed).hex() == ( + "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c") + sig = ed25519.signature(msg, seed, ed25519.publickey(seed)) + assert sig.hex() == ( + "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da" + "085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00") + assert ed25519.checkvalid(sig, msg, ed25519.publickey(seed)) is True + + +def test_roundtrip_and_verify_file(tmp_path): + seed = os.urandom(32) + pk = ed25519.publickey(seed) + installer = tmp_path / "install.sh" + installer.write_bytes(b"#!/bin/sh\necho hello\n") + data = installer.read_bytes() + sig = ed25519.signature(data, seed, pk) + sig_path = _sig_file(tmp_path, sig) + assert verify_main.verify_file(str(installer), sig_path, pk.hex()) == 0 + + +def test_fail_closed_tampered_file(tmp_path): + seed = os.urandom(32) + pk = ed25519.publickey(seed) + installer = tmp_path / "install.sh" + installer.write_bytes(b"#!/bin/sh\necho hello\n") + sig = ed25519.signature(installer.read_bytes(), seed, pk) + sig_path = _sig_file(tmp_path, sig) + installer.write_bytes(b"#!/bin/sh\nrm -rf ~\n") # attacker swaps the bytes + assert verify_main.verify_file(str(installer), sig_path, pk.hex()) == 1 + + +def test_fail_closed_wrong_key(tmp_path): + seed = os.urandom(32) + pk = ed25519.publickey(seed) + installer = tmp_path / "install.sh" + installer.write_bytes(b"payload") + sig_path = _sig_file(tmp_path, ed25519.signature(installer.read_bytes(), seed, pk)) + attacker_pk = ed25519.publickey(os.urandom(32)) + assert verify_main.verify_file(str(installer), sig_path, attacker_pk.hex()) == 1 + + +def test_fail_closed_flipped_sig_bit(tmp_path): + seed = os.urandom(32) + pk = ed25519.publickey(seed) + installer = tmp_path / "install.sh" + installer.write_bytes(b"payload") + sig = bytearray(ed25519.signature(installer.read_bytes(), seed, pk)) + sig[0] ^= 0x01 + sig_path = _sig_file(tmp_path, bytes(sig)) + assert verify_main.verify_file(str(installer), sig_path, pk.hex()) == 1 + + +def test_fail_closed_truncated_sig(tmp_path): + seed = os.urandom(32) + pk = ed25519.publickey(seed) + installer = tmp_path / "install.sh" + installer.write_bytes(b"payload") + sig = ed25519.signature(installer.read_bytes(), seed, pk)[:-1] + sig_path = _sig_file(tmp_path, sig) + assert verify_main.verify_file(str(installer), sig_path, pk.hex()) == 1 + + +def test_fail_closed_placeholder_key(tmp_path): + # The shipped default (all-zero placeholder) must never verify anything. + seed = os.urandom(32) + pk = ed25519.publickey(seed) + installer = tmp_path / "install.sh" + installer.write_bytes(b"payload") + sig_path = _sig_file(tmp_path, ed25519.signature(installer.read_bytes(), seed, pk)) + placeholder = "0" * 64 + assert verify_main.verify_file(str(installer), sig_path, placeholder) == 3 + assert verify_main.verify_file(str(installer), sig_path, "") == 3 + assert verify_main.verify_file(str(installer), sig_path, "not-hex") == 3 + + +def test_committed_pubkey_is_valid_or_placeholder(): + # Guard: release_pubkey.py must be exactly 64 hex chars (a real key) or the + # all-zero placeholder — never malformed (which would silently disable updates + # in a confusing way vs. the documented fail-closed placeholder). + from release_pubkey import RELEASE_PUBKEY_HEX + v = RELEASE_PUBKEY_HEX.strip() + assert len(v) == 64 + int(v, 16) # raises if not hex + + +def test_cross_check_pynacl(tmp_path): + try: + from nacl.signing import SigningKey, VerifyKey + except ImportError: + import pytest + pytest.skip("pynacl not installed") + seed = os.urandom(32) + pk = ed25519.publickey(seed) + msg = os.urandom(200) + # our signature verifies under pynacl + VerifyKey(pk).verify(msg, ed25519.signature(msg, seed, pk)) + # pynacl's signature verifies under ours + sk = SigningKey(seed) + assert bytes(sk.verify_key) == pk + assert ed25519.checkvalid(sk.sign(msg).signature, msg, pk) is True From fc4214882f30529361d7af2e6ad22ebe48262328 Mon Sep 17 00:00:00 2001 From: nullarch Date: Wed, 8 Jul 2026 14:22:24 +0900 Subject: [PATCH 2/3] fix(security): use a working disclosure contact (admin@streamize.net) --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 16d9cf2..b2784ec 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -36,7 +36,7 @@ and cut a release. **Do not file a public issue or pull request.** -Email **security@streamize.net** with: +Email **admin@streamize.net** with: - A description of the issue - Steps to reproduce From 4b05a3034c5c0966511662d395061a0d055c39df Mon Sep 17 00:00:00 2001 From: nullarch Date: Wed, 8 Jul 2026 14:22:25 +0900 Subject: [PATCH 3/3] fix(security): use a working disclosure contact (admin@streamize.net) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c06360..24c8271 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ VERSION bumps. A tampered or unsigned artifact is never executed. ## Reporting a vulnerability -See [SECURITY.md](SECURITY.md). TL;DR — email security@streamize.net, +See [SECURITY.md](SECURITY.md). TL;DR — email admin@streamize.net, do not file a public issue. We respond within 72 hours. ---