Security hardening and release readiness
ober
c12aa89ee863ae8750496cd651c380156064c135
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +env: + JERBOA_VERSION: v0.2.3 + JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install system tools + run: | + set -eu + sudo apt-get update + sudo apt-get install -y --no-install-recommends build-essential git curl ca-certificates + command -v cargo + cargo --version + + - name: Install Jerboa toolchain + run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Verify + run: make verify + + - name: Release evidence + run: make release-evidence new file mode 100644 --- /dev/null +++ b/.github/workflows/security-baseline.yml @@ -0,0 +1,35 @@ +name: Security Baseline + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + baseline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Required release files + run: | + set -eu + test -f LICENSE + test -f SECURITY.md + test -f .gitignore + find . -maxdepth 1 -iname "README*" -type f | grep -q . + + - name: High-confidence secret scan + run: | + set -eu + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})" + matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)" + if [ -n "$matches" ]; then + echo "$matches" + echo "High-confidence secret pattern found." + exit 1 + fi --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ signal_tui_shim.so signal_log_shim.dylib signal_log_shim.so *.db +*.trace +/trace +/dist/ --- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -3,7 +3,7 @@ "repo": "jerboa-signal", "extends": ["jerboa:cli", "jerboa:network-client", "jerboa:crypto"], "paths": { - "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "src/**/*.{ss,sls,c,h,rs}", "Makefile"], + "production": ["*.ss", "*.sls", "signal/**/*.{ss,sls,c,h}", "scripts/**/*.sh", "Makefile"], "tests": ["test/**", "tests/**", "**/*-test.ss"], "generated": ["build/**", "dist/**", "target/**", "*.so", "*.dylib", "*.wpo"], "vendor": ["vendor/**", "third_party/**"], @@ -12,9 +12,13 @@ "policy": { "failOn": ["critical", "high"], "imports": { "directChezscheme": "deny" }, - "ffi": { "allowed": false }, + "ffi": { "allowed": true, "requireLazyLoading": true, "requireBoundaryDocs": true }, "process": { "shellInterpolation": "deny" }, "network": { "requireTimeouts": true }, + "releaseEvidence": { + "targetLiveProof": "marker-validated-fail-closed", + "signalCliAdvisoryReview": "version-mismatch-fail-closed" + }, "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }, "secrets": { "forbidLogging": true, "strictStateFilePermissions": true } }, new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + 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, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --- a/Makefile +++ b/Makefile @@ -1,8 +1,7 @@ -# jerbuild bundles Chez Scheme, the jerboa stdlib, and the jerboa-native-rs -# source under ~/.cache/jerbuild/, so building jerboa-signal needs no jerboa -# source checkout and no separately-built Chez. Cargo is used to build the -# bundled native archive when it is not already present. -JERBOA_VERSION ?= v0.2.0 +# jerbuild bundles Chez Scheme and the Jerboa stdlib. When a sibling Jerboa +# checkout is present, use its patched jerboa-native-rs crate for the encrypted +# log backend; otherwise fall back to the native crate bundled with jerbuild. +JERBOA_VERSION ?= v0.2.3 JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin JERBUILD ?= $(shell if [ -x ./jerbuild ] && [ -x ./jerboa ]; then \ printf '%s\n' ./jerbuild; \ @@ -20,9 +19,15 @@ JSQLITE_REPO ?= $(VENDOR)/jsqlite JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite JSQLITE_SRC ?= $(JSQLITE_REPO)/src LIBDIRS = --libdirs $(CURDIR):$(JSQLITE_SRC):$(JH)/lib -DEFAULT_JERBOA_NATIVE_A = $(JH)/jerboa-native-rs/target/release/libjerboa_native.a +LOCAL_JERBOA_DIR ?= $(CURDIR)/../jerboa +DEFAULT_JERBOA_NATIVE_MANIFEST = $(if $(wildcard $(LOCAL_JERBOA_DIR)/jerboa-native-rs/Cargo.toml),$(LOCAL_JERBOA_DIR)/jerboa-native-rs/Cargo.toml,$(JH)/jerboa-native-rs/Cargo.toml) +JERBOA_NATIVE_MANIFEST ?= $(DEFAULT_JERBOA_NATIVE_MANIFEST) +JERBOA_NATIVE_CRATE_DIR = $(patsubst %/Cargo.toml,%,$(JERBOA_NATIVE_MANIFEST)) +DEFAULT_JERBOA_NATIVE_DIR = $(JERBOA_NATIVE_CRATE_DIR)/target/release +DEFAULT_JERBOA_NATIVE_A = $(DEFAULT_JERBOA_NATIVE_DIR)/libjerboa_native.a JERBOA_NATIVE_A ?= $(DEFAULT_JERBOA_NATIVE_A) -JERBOA_NATIVE_LIB ?= $(JH)/jerboa-native-rs/target/release/libjerboa_native.$(TUI_SHIM_EXT) +JERBOA_NATIVE_LIB ?= $(DEFAULT_JERBOA_NATIVE_DIR)/libjerboa_native.$(TUI_SHIM_EXT) +CARGO_AUDIT ?= $(shell command -v cargo-audit 2>/dev/null || printf '%s/.cargo/bin/cargo-audit' "$$HOME") export JERBOA_NATIVE_A JEXEC = JERBOA_NATIVE_LIB="$(JERBOA_NATIVE_LIB)" $(JERBUILD) exec $(LIBDIRS) BIN := jerboa-signal @@ -37,8 +42,21 @@ endif TUI_SHIM := $(TUI_SHIM_DIR)/signal_tui_shim.$(TUI_SHIM_EXT) LOG_SHIM := signal_log_shim.$(TUI_SHIM_EXT) SQLCIPHER_PREFIX := $(shell brew --prefix sqlcipher 2>/dev/null) +DIST_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +SOAK_DIR ?= dist/soak +BINARY_SMOKE_DIR ?= dist/binary-smoke +SIGNAL_CLI_ADVISORY_DIR ?= dist/signal-cli-advisory +SIGNAL_CLI ?= $(shell if [ -x "$(HOME)/.local/bin/signal-cli-jvm" ]; then \ + printf '%s\n' "$(HOME)/.local/bin/signal-cli-jvm"; \ +elif command -v signal-cli >/dev/null 2>&1; then \ + command -v signal-cli; \ +else \ + printf '%s\n' signal-cli; \ +fi) -.PHONY: all build binary run run-tui test install install-log-shim clean help vendor-deps tui-shim log-shim ensure-jerboa-tools ensure-jerboa-native ensure-jsqlite +.PHONY: all build binary run run-tui test security audit sbom reproducibility-report soak-evidence binary-smoke signal-cli-advisory-check verify release-evidence install install-log-shim clean help vendor-deps tui-shim log-shim ensure-jerboa-tools ensure-jerboa-native ensure-jsqlite .DEFAULT_GOAL := help all: binary @@ -63,6 +81,108 @@ test: binary $(JEXEC) tests/test-history-replay.ss ./$(BIN) --help >/dev/null && echo "smoke ok" +security: + sh scripts/security-check.sh + +audit: ensure-jerboa-native + @mkdir -p "$(DIST_DIR)" + @if "$(SIGNAL_CLI)" --version > "$(DIST_DIR)/signal-cli-version.txt" 2>&1; then \ + echo "signal_cli=$(SIGNAL_CLI)" >> "$(DIST_DIR)/signal-cli-version.txt"; \ + else \ + echo "signal-cli unavailable via SIGNAL_CLI=$(SIGNAL_CLI); live Signal smoke is a release blocker." > "$(DIST_DIR)/signal-cli-version.txt"; \ + fi + @if [ -x "$(CARGO_AUDIT)" ]; then \ + cd "$(JERBOA_NATIVE_CRATE_DIR)" && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-jerboa-native.txt"; \ + else \ + echo "cargo-audit not installed; CI installs it for the native dependency audit gate." > "$(DIST_DIR)/rustsec-jerboa-native.txt"; \ + fi + +sbom: + JSIGNAL_SBOM_DIR="$(SBOM_DIR)" \ + JERBUILD="$(JERBUILD)" \ + BIN="$(BIN)" \ + JERBOA_NATIVE_MANIFEST="$(JERBOA_NATIVE_MANIFEST)" \ + JERBOA_NATIVE_A="$(JERBOA_NATIVE_A)" \ + JSQLITE_SRC="$(JSQLITE_SRC)" \ + SIGNAL_CLI="$(SIGNAL_CLI)" \ + sh scripts/sbom.sh + +reproducibility-report: + JSIGNAL_REPRO_DIR="$(REPRO_DIR)" \ + JERBUILD="$(JERBUILD)" \ + BIN="$(BIN)" \ + JERBOA_NATIVE_MANIFEST="$(JERBOA_NATIVE_MANIFEST)" \ + JERBOA_NATIVE_A="$(JERBOA_NATIVE_A)" \ + JSQLITE_SRC="$(JSQLITE_SRC)" \ + sh scripts/reproducibility-report.sh + +soak-evidence: + JSIGNAL_SOAK_DIR="$(SOAK_DIR)" \ + SIGNAL_CLI="$(SIGNAL_CLI)" \ + JSIGNAL_TARGET_LIVE_PROOF_FILE="$(JSIGNAL_TARGET_LIVE_PROOF_FILE)" \ + JSIGNAL_REQUIRE_TARGET_LIVE_PROOF="$(JSIGNAL_REQUIRE_TARGET_LIVE_PROOF)" \ + sh scripts/soak-evidence.sh + +binary-smoke: + @if [ ! -x "./$(BIN)" ]; then $(MAKE) --no-print-directory binary; fi + JSIGNAL_BINARY_SMOKE_DIR="$(BINARY_SMOKE_DIR)" \ + BIN="$(BIN)" \ + sh scripts/binary-smoke.sh + +signal-cli-advisory-check: + JSIGNAL_SIGNAL_CLI_ADVISORY_DIR="$(SIGNAL_CLI_ADVISORY_DIR)" \ + SIGNAL_CLI="$(SIGNAL_CLI)" \ + JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE="$(JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE)" \ + JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF="$(JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF)" \ + sh scripts/signal-cli-advisory-check.sh + +verify: test security audit signal-cli-advisory-check + +release-evidence: verify reproducibility-report sbom soak-evidence binary-smoke + @rm -rf "$(DIST_DIR)" + @mkdir -p "$(DIST_DIR)" + @git status --short > "$(DIST_DIR)/git-status.txt" + @git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt" 2>/dev/null || true + @{ \ + echo "date=$$(date -u +%Y-%m-%dT%H:%M:%SZ)"; \ + echo "uname=$$(uname -a)"; \ + echo "jerbuild=$(JERBUILD)"; \ + echo "jerboa_home=$(JH)"; \ + "$(JERBUILD)" --version 2>/dev/null || true; \ + } > "$(DIST_DIR)/build-environment.txt" + @if "$(SIGNAL_CLI)" --version > "$(DIST_DIR)/signal-cli-version.txt" 2>&1; then \ + echo "signal_cli=$(SIGNAL_CLI)" >> "$(DIST_DIR)/signal-cli-version.txt"; \ + else \ + echo "signal-cli unavailable via SIGNAL_CLI=$(SIGNAL_CLI); live Signal smoke is a release blocker." > "$(DIST_DIR)/signal-cli-version.txt"; \ + fi + @find signal tests -type f \( -name '*.ss' -o -name '*.c' -o -name '*.h' \) -print | sort | xargs shasum -a 256 > "$(DIST_DIR)/source-sha256.txt" + @find vendor/jsqlite/src -type f -name '*.ss' -print | sort | xargs shasum -a 256 > "$(DIST_DIR)/jsqlite-sha256.txt" + @shasum -a 256 Makefile .jerbuild .jerboa/security.json SECURITY.md docs/threat-model.md docs/credential-handling.md docs/ffi-boundary.md docs/release-evidence.md docs/signal-cli-advisory-review.md scripts/security-check.sh scripts/sbom.sh scripts/reproducibility-report.sh scripts/soak-evidence.sh scripts/binary-smoke.sh scripts/signal-cli-advisory-check.sh > "$(DIST_DIR)/release-inputs-sha256.txt" + @shasum -a 256 "$(BIN)" > "$(DIST_DIR)/binary-sha256.txt" + @if command -v otool >/dev/null 2>&1; then otool -L "$(BIN)" > "$(DIST_DIR)/native-linkage.txt"; elif command -v ldd >/dev/null 2>&1; then ldd "$(BIN)" > "$(DIST_DIR)/native-linkage.txt"; else echo "no otool/ldd available" > "$(DIST_DIR)/native-linkage.txt"; fi + @if command -v cargo >/dev/null 2>&1 && [ -f "$(JERBOA_NATIVE_MANIFEST)" ]; then \ + cargo metadata --format-version 1 --manifest-path "$(JERBOA_NATIVE_MANIFEST)" > "$(DIST_DIR)/cargo-metadata-jerboa-native.json"; \ + fi + @if [ -x "$(CARGO_AUDIT)" ] && [ -f "$(JERBOA_NATIVE_MANIFEST)" ]; then \ + cd "$(JERBOA_NATIVE_CRATE_DIR)" && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-jerboa-native.txt"; \ + else \ + echo "cargo-audit not installed or jerboa-native-rs manifest missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-jerboa-native.txt"; \ + fi + @$(MAKE) --no-print-directory signal-cli-advisory-check + @rm -rf "$(DIST_DIR)/sbom" "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/soak" "$(DIST_DIR)/binary-smoke" "$(DIST_DIR)/signal-cli-advisory" + @cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom" + @cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility" + @cp -R "$(SOAK_DIR)" "$(DIST_DIR)/soak" + @cp -R "$(BINARY_SMOKE_DIR)" "$(DIST_DIR)/binary-smoke" + @cp -R "$(SIGNAL_CLI_ADVISORY_DIR)" "$(DIST_DIR)/signal-cli-advisory" + @grep -q '^status=match$$' "$(DIST_DIR)/reproducibility/result.txt" + @grep -q '^status=pass$$' "$(DIST_DIR)/binary-smoke/status.txt" + @if grep -q '^status=failed-current$$' "$(DIST_DIR)/soak/status.txt"; then \ + echo "current Signal live-smoke evidence failed; see $(DIST_DIR)/soak/status.txt"; \ + exit 1; \ + fi + @echo "release evidence written to $(DIST_DIR)" + install: binary mkdir -p $(BIN_DIR) install -m 0755 $(BIN) $(BIN_DIR)/$(BIN) @@ -87,6 +207,7 @@ clean: rm -f signal_tui_shim.dylib signal_tui_shim.so rm -f signal_log_shim.dylib signal_log_shim.so find signal \( -name '*.so' -o -name '*.wpo' \) -delete 2>/dev/null || true + rm -rf dist vendor-deps: ensure-jsqlite vendor/termbox2 @@ -123,8 +244,8 @@ ensure-jerboa-native: ensure-jerboa-tools echo "ERROR: JERBOA_NATIVE_A is set but missing: $(JERBOA_NATIVE_A)"; \ exit 1; \ fi; \ - if [ ! -f "$(JH)/jerboa-native-rs/Cargo.toml" ]; then \ - echo "ERROR: bundled jerboa-native-rs source not found under $(JH)"; \ + if [ ! -f "$(JERBOA_NATIVE_MANIFEST)" ]; then \ + echo "ERROR: jerboa-native-rs manifest not found: $(JERBOA_NATIVE_MANIFEST)"; \ exit 1; \ fi; \ command -v cargo >/dev/null 2>&1 || { \ @@ -132,7 +253,7 @@ ensure-jerboa-native: ensure-jerboa-tools exit 1; \ }; \ echo "=== Building Jerboa native archive: $(JERBOA_NATIVE_A) ==="; \ - cargo build --release --manifest-path "$(JH)/jerboa-native-rs/Cargo.toml"; \ + cargo build --release --manifest-path "$(JERBOA_NATIVE_MANIFEST)"; \ fi @test -f "$(JERBOA_NATIVE_A)" || { \ echo "ERROR: missing Jerboa native archive: $(JERBOA_NATIVE_A)"; \ @@ -166,6 +287,14 @@ help: @echo " run ARGS='<args>' Run from source via 'jerbuild exec'" @echo " run-tui Build shim and start the TUI" @echo " test Run tests" + @echo " security Run local security checks" + @echo " audit Record signal-cli/native dependency audit status" + @echo " sbom Write SBOM/provenance evidence under dist/sbom" + @echo " reproducibility-report Rebuild twice and compare release artifacts" + @echo " soak-evidence Record Signal live-smoke status without secrets" + @echo " binary-smoke Start the shipped binary through a no-secret path" + @echo " verify Run tests, security checks, and audit" + @echo " release-evidence Write release evidence under dist/release-evidence" @echo " install Install ./jerboa-signal to ~/.local/bin" @echo " install-log-shim Install SQLCipher shim for legacy logs" @echo " ensure-jerboa-tools Ensure jerboa/jerbuild are available" --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ jerboa-signal tui [-a +PHONE] # terminal UI shell `scripts/signal-cli-jvm` wrapper uses `~/.local/opt/signal-cli-0.14.5`). Older 0.14.4.1 builds drop current sealed-sender receive envelopes with `getServerGuid(...) must not be null`. + Release evidence currently reviews exactly `signal-cli 0.14.5`; a newer + installed version must be reviewed before publication. - Jerboa build tools. `make binary`/`make install` use project-local `./jerbuild` first, then `.jerboa/bin`, then `PATH`, and download the matching release artifact if none are available. @@ -114,17 +116,33 @@ jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace tui ``` That file is sensitive: it can include message content and Signal identifiers. -When debugging send hangs caused by rewriting a large encrypted log, this mode -is also useful: +To force the encrypted log to be rewritten after every row while debugging +durability issues, use: ```sh -jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace --log-persist close tui +jerboa-signal --trace-all /tmp/jerboa-signal.raw.trace --log-persist immediate tui ``` -`--log-persist close` defers jsqlite encrypted-container writes until clean -exit. The default is `immediate`, which persists after every logged message. If -the process crashes in close-only mode, new log rows from that session may be -lost. +The default is `close`, which defers jsqlite encrypted-container writes until +clean exit so the TUI does not pause while rewriting a large encrypted history. +`--log-persist immediate` persists after every logged message. If the process +crashes in close-only mode, new log rows from that session may be lost. + +## Security + +`jerboa-signal` is not a Signal protocol implementation. It delegates Signal +cryptography, account state, server communication, and device linking to +`signal-cli`. The local security boundary is the Jerboa orchestration layer: +JSON-RPC framing, trace redaction, attachment export, encrypted local message +logs, terminal UI FFI, and process execution. + +Before using this on production accounts, read `SECURITY.md`, +`docs/threat-model.md`, `docs/credential-handling.md`, and +`docs/ffi-boundary.md`. Run `make verify` from a clean checkout and preserve +`make release-evidence` output for release candidates. The release evidence +includes SBOM/provenance, repeated binary reproducibility, a no-secret binary +startup smoke, `signal-cli` advisory/version review, and explicit status fields +for live Signal smokes that require a non-production linked account. ## Encrypted message log @@ -189,4 +207,4 @@ Modules: ## License -ISC. +Apache-2.0. new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,67 @@ +# Security Policy + +`jerboa-signal` is a local Signal client wrapper around `signal-cli`. Treat it +as experimental until a release has clean local and hosted verification evidence. + +## Supported Status + +No public production-support commitment exists yet. Keep production accounts on +reviewed private builds until these gates pass from a clean checkout: + +- `make verify` +- `make release-evidence` +- clean `jerboa_security_scan` for medium-or-higher findings +- SBOM, repeated reproducibility, no-secret binary-smoke, and explicit + live-smoke status evidence from the release host +- marker-complete redacted target live-smoke proof through + `JSIGNAL_TARGET_LIVE_PROOF_FILE` and `JSIGNAL_REQUIRE_TARGET_LIVE_PROOF=1`, + or an equivalent reviewed release-host evidence bundle +- reviewed `signal-cli` version and upstream advisory posture through + `make signal-cli-advisory-check`, with required proof enforced by + `JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE` and + `JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF=1` for production release approval +- external review of local logging, trace, attachment, process, and FFI paths + +## Sensitive Data + +This repository handles Signal account identifiers, message bodies, +attachments, group names, profile/contact metadata, local `signal-cli` account +state, encrypted log passphrases, and optional raw JSON-RPC traces. + +Do not commit Signal databases, traces, attachments, exported logs, passphrases, +phone numbers from real accounts, or production operational data. The default +trace mode redacts message bodies and raw RPC frames; `--trace-all` is sensitive +and must stay local. + +## Local Storage + +Encrypted message logs live under `~/.local/share/jerboa-signal/` by default. +They are encrypted through the Jerboa native crypto backend and jsqlite +container format, but filenames, file existence, backups, and crash artifacts +can still reveal account activity. Use local disk encryption and restrict +backups for Signal client hosts. + +`JERBOA_SIGNAL_DB_KEY` is accepted for unattended sessions but exposes the log +passphrase through the process environment. Prefer interactive prompting or a +supervisor secret store. + +## External Dependencies + +Signal protocol security is delegated to `signal-cli` and its +`libsignal-service-java` dependency chain. Release candidates must record the +exact `signal-cli --version`, Java/runtime source, and advisory review status. +The dated local review currently accepts `signal-cli 0.14.5`; any different +installed `signal-cli` version requires a refreshed review before publication. +Linked-account and send/receive smoke tests must use a non-production account, +and evidence must not preserve raw account identifiers or message contents. +Missing, empty, or marker-incomplete required target live-smoke proof fails +closed as `status=blocked-target-proof`. + +The Jerboa native crypto archive is used for the encrypted log container. +Release candidates must audit the bundled `jerboa-native-rs` dependencies. + +## Reporting + +Before public release, report issues privately to the repository owner. After +public release, replace this section with a dedicated advisory contact, +supported versions, and disclosure window. new file mode 100644 --- /dev/null +++ b/docs/credential-handling.md @@ -0,0 +1,60 @@ +# jerboa-signal Credential Handling + +`jerboa-signal` should not store Signal account secrets itself. Account linking, +identity keys, sessions, and Signal service credentials are owned by +`signal-cli`. + +## Signal Account State + +- Link accounts with `signal-cli link` outside this repository. +- Keep the `signal-cli` data directory owned by the local user and inaccessible + to other users. +- Do not copy production `signal-cli` data into fixtures, traces, release + evidence, or support bundles. +- Use a dedicated non-production linked device for CI/manual release smoke. + +## Log Passphrases + +The encrypted message log prompts for a passphrase before the TUI starts. Leave +the prompt blank to disable logging for a session. + +`JERBOA_SIGNAL_DB_KEY` is supported for unattended runs. It is convenient but +less private because environment variables may be visible to supervisors, +diagnostics, shell history wrappers, or crash reports. Production deployments +should prefer interactive entry or a protected service secret store. + +Rotate the log passphrase by creating a new encrypted log and importing only the +history you still need. Treat old `.sqlcipher.bak` migration backups as +sensitive until deleted. + +## Traces And Logs + +Default tracing records timing, byte counts, backend selection, and failure +summaries without raw message bodies or RPC frames. + +`--trace-all` and `JERBOA_SIGNAL_TRACE_SENSITIVE=1` can include message bodies, +Signal identifiers, raw envelopes, stderr from `signal-cli`, and operational +state. Use them only on trusted machines, write them to mode `0600` files, and +delete them after debugging. + +## Attachments + +Attachments are copied from the local `signal-cli` attachment cache to +`~/Downloads/jerboa-signal/` or `JERBOA_SIGNAL_DOWNLOAD_DIR`. + +Destination names are sanitized and never overwrite existing files, but the +exported content is plaintext. Keep the download root outside synced or shared +directories unless that is intentional. + +## Release Checklist + +- `make verify` passes. +- No traces, logs, attachments, databases, or real account identifiers are + present in the working tree. +- `dist/release-evidence/signal-cli-version.txt` records the external client + version or explicitly records that no live Signal smoke was run. +- `dist/release-evidence/signal-cli-advisory/status.txt` records the reviewed + upstream `signal-cli` version/advisory posture, and any required proof file is + marker-complete and redacted. +- The first public release has a history secret scan or an intentional history + reset. new file mode 100644 --- /dev/null +++ b/docs/ffi-boundary.md @@ -0,0 +1,48 @@ +# jerboa-signal FFI Boundary + +`jerboa-signal` has three native-adjacent surfaces: bundled Jerboa native crypto, +the termbox2 TUI shim, and the optional legacy SQLCipher log shim. + +## Jerboa Native Crypto + +The encrypted jsqlite log container uses `jerboa_scrypt`, `jerboa_aead_seal`, +`jerboa_aead_open`, and `jerboa_last_error` from the bundled Jerboa native +archive or dynamic library. + +- Native loading is lazy through `(jerboa ffi)` `load-shared-object*`. +- Importing the log module must not load or require a dynamic library. +- Scheme passes bytevectors and explicit lengths for key derivation and AEAD. +- Output lengths return through Scheme-managed bytevectors, not `foreign-alloc`. +- Release evidence must include RustSec output or a documented local + `cargo-audit` gap for `jerboa-native-rs`. + +## termbox2 TUI Shim + +The TUI shim wraps termbox2 event polling and drawing. + +- The last-event buffer is thread-local, not process-global. +- Blocking event polling uses a collect-safe foreign call where the runtime + surface supports it. +- Terminal text remains local to the user's terminal. It can still contain + message text and contact names, so terminal scrollback is sensitive. +- Release candidates should smoke-test `make tui-shim` on every supported OS. + +## Legacy SQLCipher Shim + +The SQLCipher shim exists only to migrate older logs. + +- Loading is lazy and optional. +- The new jsqlite container is preferred for new logs. +- Migration writes a new encrypted jsqlite file, verifies the row count, and + preserves the old SQLCipher database as a sensitive backup. +- Release candidates with legacy migration support should build the shim on a + host with SQLCipher installed and record native linkage. + +## Do Not Do + +- Do not add top-level `load-shared-object` calls. +- Do not store native handles in untracked global mutable state without + ownership rules. +- Do not pass Scheme strings with embedded NUL bytes into native path or command + surfaces. +- Do not add custom Signal crypto or protocol parsing below `signal-cli`. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,111 @@ +# jerboa-signal Release Evidence + +Run from a clean checkout: + +```sh +make release-evidence +``` + +The target runs `make verify`, then writes ignored artifacts under +`dist/release-evidence/`. + +`make release-evidence` also refreshes: + +- `make reproducibility-report`, which builds the standalone binary twice with a + fixed Jerbuild object directory and compares the binary, generated inputs, + selected native archive input, and source manifest. +- `make sbom`, which records source/release input hashes, Jerboa toolchain + identity, `signal-cli` version status, jsqlite source status, and selected + `jerboa-native-rs` metadata. +- `make binary-smoke`, which starts the shipped binary through the no-secret + `--help` path and records macOS code-signature status when available. +- `make signal-cli-advisory-check`, which records the reviewed upstream + `signal-cli` release, local `signal-cli --version`, GitHub advisory posture, + and optional reviewed proof material. +- `make soak-evidence`, which records explicit live-smoke status fields without + storing Signal account identifiers, message contents, traces, or attachment + paths. + +## Evidence Files + +- `git-status.txt` and `git-commit.txt` +- `build-environment.txt` +- `signal-cli-version.txt` from `SIGNAL_CLI` or the same + `~/.local/bin/signal-cli-jvm` preference used by the runtime +- `source-sha256.txt` +- `jsqlite-sha256.txt` +- `release-inputs-sha256.txt` +- `binary-sha256.txt` +- `native-linkage.txt` +- `cargo-metadata-jerboa-native.json` when Cargo is available +- `rustsec-jerboa-native.txt` +- `sbom/` +- `signal-cli-advisory/` +- `reproducibility/` +- `binary-smoke/` +- `soak/` + +## Review Notes + +`signal-cli-version.txt` may state that `signal-cli` was unavailable through +`SIGNAL_CLI` on the build host. That is acceptable for local development, but +production release requires a separate linked non-production test-account smoke +on the supported target OS. Run the redacted account-count smoke with +`JSIGNAL_RUN_LINKED_SMOKE=1 make soak-evidence`; the script must not preserve +raw `signal-cli listAccounts` output. + +Production release also requires a current `signal-cli` upstream advisory and +version review. The local dated review currently accepts `signal-cli 0.14.5` and +records GitHub's 2026-06-23 release/advisory posture in +`docs/signal-cli-advisory-review.md`. If a different `signal-cli` version is +installed, `make signal-cli-advisory-check` fails with +`status=blocked-version-review-required`. + +External release-owner review proof can be attached as a redacted marker file: + +```sh +JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE=/path/to/signal-cli-review.txt \ +JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF=1 \ +make signal-cli-advisory-check +``` + +The proof file is marker-validated and must contain: + +- `signal_cli_review_status=reviewed` +- `signal_cli_latest_release=v0.14.5` +- `signal_cli_security_advisory_status=no-published-github-advisories` +- `upstream_security_policy_status=no-upstream-security-policy` +- `release_signature_status=github-verified-release` +- `signal_cli_update_window_status=within-reviewed-window` +- `sensitive_artifact_policy=no-signal-account-state-or-message-data` + +Missing, empty, marker-incomplete, or sensitive-looking required proof fails +closed with `status=blocked-review-proof`. Accepted proof is copied to +`signal-cli-advisory/signal-cli-review-proof.txt` with a SHA-256 sidecar. + +Release-host live-smoke proof can also be attached as a reviewed, redacted proof +file: + +```sh +JSIGNAL_TARGET_LIVE_PROOF_FILE=/path/to/target-live-proof.txt \ +JSIGNAL_REQUIRE_TARGET_LIVE_PROOF=1 \ +make soak-evidence +``` + +The proof file is marker-validated and must contain: + +- `linked_account_status=target-evidence-recorded` +- `send_receive_status=target-evidence-recorded` +- `trace_redaction_live_status=target-evidence-recorded` +- `attachment_export_live_status=target-evidence-recorded` +- `production_smoke_status=current-run-recorded` +- `production_min_seconds_met=1` +- `sensitive_artifact_policy=no-raw-account-traces-attachments-or-message-bodies` + +Missing, empty, or marker-incomplete required proofs fail closed with +`status=blocked-target-proof`. Accepted proofs are copied to +`soak/target-live-proof.txt` with a SHA-256 sidecar. + +Do not publish `dist/release-evidence/` if it was produced from a working tree +containing real Signal traces, message logs, attachments, or production account +state. new file mode 100644 --- /dev/null +++ b/docs/signal-cli-advisory-review.md @@ -0,0 +1,55 @@ +# signal-cli Advisory And Version Review + +Review date: 2026-06-23 + +`jerboa-signal` delegates Signal protocol, account state, and service +compatibility to upstream `signal-cli`. A production release must therefore +record the exact `signal-cli` version and the upstream advisory posture used for +the release. + +## Reviewed Upstream State + +- Upstream project: <https://github.com/AsamK/signal-cli> +- Reviewed release page: <https://github.com/AsamK/signal-cli/releases> +- Reviewed GitHub security page: <https://github.com/AsamK/signal-cli/security> +- Reviewed latest release: `v0.14.5` +- Reviewed release date: 2026-06-11 +- Reviewed release commit shown by GitHub: `6bef205` +- GitHub release signature status shown by GitHub: verified +- GitHub security-advisory status shown by GitHub: no published advisories +- Upstream security-policy status shown by GitHub: no `SECURITY.md` policy + configured + +The lack of a published GitHub advisory is not a guarantee that the dependency +is free of vulnerabilities. It is a dated release-owner review signal. The lack +of an upstream security policy means Jerboa release owners must keep this +dependency private until an explicit update and vulnerability-monitoring process +is in place. + +## Release Requirements + +`make signal-cli-advisory-check` records: + +- local `signal-cli --version` output when available; +- whether the local version matches the reviewed upstream release; +- the dated upstream advisory/security-policy posture above; +- optional reviewed proof material supplied through + `JSIGNAL_SIGNAL_CLI_REVIEW_PROOF_FILE`. + +If a local `signal-cli` binary is present and its version is not the reviewed +version, the check fails with `status=blocked-version-review-required`. + +If `JSIGNAL_REQUIRE_SIGNAL_CLI_REVIEW_PROOF=1` is set, the proof file must be +non-empty and marker-complete. Required markers are: + +- `signal_cli_review_status=reviewed` +- `signal_cli_latest_release=v0.14.5` +- `signal_cli_security_advisory_status=no-published-github-advisories` +- `upstream_security_policy_status=no-upstream-security-policy` +- `release_signature_status=github-verified-release` +- `signal_cli_update_window_status=within-reviewed-window` +- `sensitive_artifact_policy=no-signal-account-state-or-message-data` + +The proof file must not contain Signal account state, phone numbers, message +bodies, raw traces, attachments, private keys, API tokens, or other operational +secrets. Accepted proof is copied into release evidence with a SHA-256 sidecar. new file mode 100644 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,73 @@ +# jerboa-signal Threat Model + +`jerboa-signal` is a local Signal client wrapper. It is not the protocol TCB: +Signal transport, device linking, account state, and end-to-end cryptography are +delegated to `signal-cli`. + +## Assets + +- Signal account identifiers, phone numbers, UUIDs, profile names, group IDs, + group names, and contact metadata. +- Message bodies, attachments, reactions, edits, deletes, receipts, calls, and + typing events. +- Local `signal-cli` account state under the user's Signal CLI data directory. +- Encrypted jsqlite message logs, legacy SQLCipher logs, log backups, WAL/SHM + sidecars, removed-conversation state, theme files, and attachment exports. +- Log passphrases from interactive input or `JERBOA_SIGNAL_DB_KEY`. +- Optional trace files, especially `--trace-all` raw RPC traces. + +## Trust Boundaries + +- Jerboa starts and drives `signal-cli jsonRpc` as a subprocess. Treat stdout, + stderr, and every JSON-RPC frame as hostile input. +- The terminal UI crosses into a termbox2 C shim for rendering and input. +- Encrypted logs cross into the bundled Jerboa native crypto backend and jsqlite + parser/writer. +- Legacy log migration can cross into an optional SQLCipher shim. +- Attachment export copies files from `signal-cli`'s attachment directory into a + user-visible download tree. + +## Entry Points + +- CLI args: `send`, `listen`, `tui`, `migrate-log`, `--trace`, `--trace-all`, + `--log-persist`, and `-a`. +- Environment variables: `JERBOA_SIGNAL_CLI`, `JERBOA_SIGNAL_CLI_LOG`, + `JERBOA_SIGNAL_CLI_VERBOSE`, `JERBOA_SIGNAL_TRACE`, + `JERBOA_SIGNAL_TRACE_SENSITIVE`, `JERBOA_SIGNAL_DB_KEY`, + `JERBOA_SIGNAL_LOG_BACKEND`, `JERBOA_SIGNAL_LOG_PERSIST`, + `JERBOA_SIGNAL_DOWNLOAD_DIR`, and `JERBOA_SIGNAL_CLI_DATA_DIR`. +- Local files: `signal-cli` account state, attachments, encrypted logs, + migration backups, trace files, removed-conversation state, and themes. +- Native libraries: bundled Jerboa native crypto, optional termbox2 shim, and + optional legacy SQLCipher shim. + +## Required Mitigations + +- Do not implement Signal protocol cryptography in this repo. Delegate to + reviewed `signal-cli` and record the version in release evidence. +- Use argv process APIs for `signal-cli` execution. Do not construct shell + command strings from contacts, messages, paths, or account identifiers. +- Keep traces redacted by default. Raw traces must be opt-in and marked + sensitive in docs and release notes. +- Write state and trace files through checked paths and safe wrappers. State + files created by this app must use mode `0600` when they contain account or + preference state. +- Sanitize attachment destination directories and filenames, never overwrite + existing downloads, and keep exports under an explicit user download root. +- Load native libraries lazily through `(jerboa ffi)` so import and static + binary startup do not crash before user policy can run. +- Keep C shim mutable event buffers thread-local or caller-owned. + +## Residual Production Blockers + +- Hosted CI evidence for Linux, including `signal-cli` installation and CLI + smoke tests against a non-production linked test account. +- Upstream `signal-cli` advisory review and documented supported version range. +- History secret scan before first public release. +- Target-host linked-account, send/receive, trace-redaction, and attachment