Security hardening and release readiness
ober
08b37ba597c5e667c12d95fd0d4a61acc7b9b8ff
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +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 + JERBOA_SSL_REPO: https://git.sr.ht/~lisp/jerboa-ssl + JERBOA_MAIL_REPO: https://git.sr.ht/~lisp/jerboa-mail + JERBOA_SSL_DIR: ${{ github.workspace }}/.deps/jerboa-ssl + JERBOA_MAIL_DIR: ${{ github.workspace }}/.deps/jerboa-mail + +jobs: + build-and-test: + 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 libssl-dev + + - name: Install Jerboa toolchain + run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + + - name: Fetch dependencies + run: | + set -eu + mkdir -p .deps + git clone --depth 1 "$JERBOA_SSL_REPO" "$JERBOA_SSL_DIR" + git clone --depth 1 "$JERBOA_MAIL_REPO" "$JERBOA_MAIL_DIR" + + - 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 @@ -7,6 +7,11 @@ *.log *.eml *.mbox +/.deps/ +/.jerboa/* +!/.jerboa/ +!/.jerboa/security.json +/dist/ /protonmail-read /protonmail-read-bin .protonmail-reader.local new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,48 @@ +{ + "version": 1, + "repo": "jerboa-protonmail", + "extends": ["jerboa:cli", "jerboa:network-client", "jerboa:credentialed-client", "jerboa:mail-client"], + "paths": { + "production": ["*.ss", "*.sls", "bin/**", "protonmail/**/*.ss", "protonmail/**/*.sls", "Makefile"], + "tests": ["test/**", "tests/**", "**/*-test.ss"], + "generated": ["build/**", "dist/**", "target/**", "*.so", "*.dylib", "*.wpo", "*.boot"], + "vendor": ["vendor/**", ".deps/**"], + "docs": ["README.md", "docs/**", "*.md"] + }, + "policy": { + "failOn": ["critical", "high"], + "imports": { "directChezscheme": "deny" }, + "ffi": { "allowed": false }, + "process": { "shellInterpolation": "deny" }, + "network": { "requireTimeouts": true, "rawMailOutputEvidence": "deny" }, + "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }, + "credentials": { + "plaintextCache": "deny", + "rawCredentialEvidence": "deny", + "protonAccountPasswordUse": "deny", + "bridgeCredentialStorage": "environment-only" + }, + "mail": { + "defaultMutations": "deny", + "requireBodyPeek": true, + "plaintextExportEvidence": "target-required" + }, + "targetEvidence": { + "bridgeProof": "fail-closed", + "requiredMarkers": [ + "bridge_doctor_status=target-evidence-recorded", + "bridge_loopback_status=confirmed", + "bridge_generated_credential_status=confirmed", + "bridge_credential_redaction_status=target-evidence-recorded", + "read_only_imap_status=target-evidence-recorded", + "body_peek_policy_status=confirmed", + "plaintext_export_review_status=target-evidence-recorded", + "target_platform_smoke_status=target-evidence-recorded", + "production_mail_review_status=current-run-recorded", + "raw_mail_output_status=not-preserved", + "sensitive_artifact_policy=no-proton-account-passwords-bridge-passwords-eml-mbox-mail-bodies-attachments-or-imap-transcripts-preserved" + ] + } + }, + "suppressions": [] +} 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,10 +1,34 @@ -JERBOA_HOME ?= $(realpath $(CURDIR)/../jerboa) -SCHEME ?= $(JERBOA_HOME)/.chez/bin/scheme -JERBOA_SSL_DIR ?= $(realpath $(CURDIR)/../jerboa-ssl) -JERBOA_MAIL_DIR ?= $(realpath $(CURDIR)/../jerboa-mail) -LIBDIRS := $(CURDIR):$(JERBOA_MAIL_DIR):$(JERBOA_SSL_DIR)/lib:$(JERBOA_HOME)/lib +JERBOA_VERSION ?= v0.2.3 +JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin +JERBUILD ?= $(shell if [ -x "$(CURDIR)/jerbuild" ]; then echo "$(CURDIR)/jerbuild"; \ + elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \ + elif [ -x "$(CURDIR)/../jerboa/dist/jerbuild" ]; then echo "$(CURDIR)/../jerboa/dist/jerbuild"; \ + elif [ -x "$(CURDIR)/../jerboa/jerbuild" ]; then echo "$(CURDIR)/../jerboa/jerbuild"; \ + elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; \ + else echo "$(JERBOA_TOOL_DIR)/jerbuild"; fi) +JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null) -.PHONY: help run test integration-test doctor clean +DEPS_DIR ?= $(CURDIR)/.deps +DIST_DIR ?= $(CURDIR)/dist +RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence +TARGET_EVIDENCE_DIR ?= $(DIST_DIR)/target-evidence +LOCAL_SSL_DIR := $(CURDIR)/../jerboa-ssl +LOCAL_MAIL_DIR := $(CURDIR)/../jerboa-mail +JERBOA_SSL_DIR ?= $(if $(wildcard $(LOCAL_SSL_DIR)/.),$(realpath $(LOCAL_SSL_DIR)),$(DEPS_DIR)/jerboa-ssl) +JERBOA_MAIL_DIR ?= $(if $(wildcard $(LOCAL_MAIL_DIR)/.),$(realpath $(LOCAL_MAIL_DIR)),$(DEPS_DIR)/jerboa-mail) +LIBDIRS = $(CURDIR):$(JERBOA_MAIL_DIR):$(JERBOA_SSL_DIR)/lib:$(JH)/lib + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + LD_VAR = DYLD_LIBRARY_PATH + SO_EXT = .dylib +else + LD_VAR = LD_LIBRARY_PATH + SO_EXT = .so +endif +LD_ENV = $(LD_VAR)="$(JERBOA_SSL_DIR)" + +.PHONY: help ensure-jerboa-tools ensure-deps deps build run test integration-test doctor verify security dependency-evidence sbom reproducibility-report target-evidence target-evidence-selftest release-evidence clean .DEFAULT_GOAL := help help: @@ -12,30 +36,57 @@ help: @echo "" @echo "Development:" @echo " bin/protonmail-read --help Run the checked-in launcher" - @echo " make run ARGS='--help' Run the CLI under the interpreter" + @echo " make run ARGS='--help' Run the CLI through jerbuild" @echo " make doctor Run the placeholder doctor command" @echo " make test Run smoke tests" @echo " make integration-test Run Bridge doctor when PROTON_BRIDGE_INTEGRATION=1" + @echo " make verify Run release verification checks" + @echo " make target-evidence Record live Proton Bridge target proof status" + @echo " make release-evidence Write test/dependency/SBOM evidence under dist/" @echo " make clean Remove generated local artifacts" @echo "" @echo "Environment:" - @echo " JERBOA_HOME = $(JERBOA_HOME)" - @echo " SCHEME = $(SCHEME)" + @echo " JERBUILD = $(JERBUILD)" @echo " JERBOA_SSL_DIR = $(JERBOA_SSL_DIR)" @echo " JERBOA_MAIL_DIR = $(JERBOA_MAIL_DIR)" -run: - JERBOA_HOME=$(JERBOA_HOME) JERBOA_SSL_LIB=$(JERBOA_SSL_DIR) \ - $(SCHEME) -q --libdirs $(LIBDIRS) \ - --script main.ss -- $(ARGS) +ensure-jerboa-tools: + @if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \ + echo "=== Using Jerboa toolchain: $(JERBUILD) ==="; \ + elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && "$(JERBOA_TOOL_DIR)/jerbuild" --jerboa-home >/dev/null 2>&1; then \ + echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \ + elif command -v jerbuild >/dev/null 2>&1 && jerbuild --jerboa-home >/dev/null 2>&1; then \ + echo "=== Using Jerboa toolchain from PATH: $$(command -v jerbuild) ==="; \ + else \ + echo "=== Fetching Jerboa $(JERBOA_VERSION) release tools into $(JERBOA_TOOL_DIR) ==="; \ + sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \ + fi + @"$(JERBUILD)" --jerboa-home >/dev/null || { \ + echo "ERROR: Jerboa toolchain is unavailable; set JERBUILD=/path/to/jerbuild or JERBOA_VERSION=<tag>" >&2; \ + exit 1; \ + } + +ensure-deps: + @test -d "$(JERBOA_SSL_DIR)" || { echo "ERROR: JERBOA_SSL_DIR missing: $(JERBOA_SSL_DIR)" >&2; exit 1; } + @test -d "$(JERBOA_MAIL_DIR)" || { echo "ERROR: JERBOA_MAIL_DIR missing: $(JERBOA_MAIL_DIR)" >&2; exit 1; } + +deps: ensure-jerboa-tools ensure-deps + @if [ ! -f "$(JERBOA_SSL_DIR)/lib/jerboa-ssl.sls" ] || [ ! -f "$(JERBOA_SSL_DIR)/jerboa_ssl_shim$(SO_EXT)" ]; then \ + $(MAKE) -C "$(JERBOA_SSL_DIR)" JERBUILD="$(JERBUILD)" build; \ + fi + +build: deps + +run: build + JERBOA_SSL_LIB="$(JERBOA_SSL_DIR)" $(LD_ENV) \ + "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" main.ss -- $(ARGS) doctor: $(MAKE) run ARGS='doctor' -test: - JERBOA_HOME=$(JERBOA_HOME) JERBOA_SSL_LIB=$(JERBOA_SSL_DIR) \ - $(SCHEME) -q --libdirs $(LIBDIRS) \ - --script test/test-all.ss +test: build + JERBOA_SSL_LIB="$(JERBOA_SSL_DIR)" $(LD_ENV) \ + "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" test/test-all.ss integration-test: @if [ "$$PROTON_BRIDGE_INTEGRATION" != "1" ]; then \ @@ -44,6 +95,78 @@ integration-test: bin/protonmail-read doctor; \ fi +verify: test + +security: + @set -eu; \ + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|gldt-[A-Za-z0-9_-]{20,}|glrt-[A-Za-z0-9_-]{20,}|PROTON_BRIDGE_PASSWORD=['\\\"]?[A-Za-z0-9_./+=:-]{32,}|AKIA[0-9A-Z]{16}|ASIA[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; \ + echo "secret_scan_status=pass" + +dependency-evidence: deps + $(MAKE) -C "$(JERBOA_SSL_DIR)" JERBUILD="$(JERBUILD)" release-evidence + $(MAKE) -C "$(JERBOA_MAIL_DIR)" JERBUILD="$(JERBUILD)" release-evidence + +sbom: ensure-jerboa-tools ensure-deps + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + JERBUILD="$(JERBUILD)" \ + JERBOA_SSL_DIR="$(JERBOA_SSL_DIR)" \ + JERBOA_MAIL_DIR="$(JERBOA_MAIL_DIR)" \ + sh scripts/sbom.sh + +reproducibility-report: + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + sh scripts/reproducibility-report.sh + +target-evidence: + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" \ + sh scripts/target-evidence.sh + +target-evidence-selftest: + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + sh scripts/test-target-evidence.sh + +release-evidence: + @rm -rf "$(RELEASE_EVIDENCE_DIR)" "$(TARGET_EVIDENCE_DIR)" "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/sbom" + @mkdir -p "$(RELEASE_EVIDENCE_DIR)" + @echo "==> Collecting git status" + @git status --short > "$(RELEASE_EVIDENCE_DIR)/git-status.txt" + @git rev-parse HEAD > "$(RELEASE_EVIDENCE_DIR)/git-head.txt" 2>/dev/null || true + @echo "==> Running high-confidence secret scan" + @$(MAKE) security > "$(RELEASE_EVIDENCE_DIR)/security.log" 2>&1 + @echo "==> Running tests" + @$(MAKE) test > "$(RELEASE_EVIDENCE_DIR)/test.log" 2>&1 + @echo "==> Running CLI help smoke" + @$(MAKE) run ARGS='--help' > "$(RELEASE_EVIDENCE_DIR)/help.log" 2>&1 + @echo "==> Running dependency release evidence" + @$(MAKE) dependency-evidence > "$(RELEASE_EVIDENCE_DIR)/dependency-evidence.log" 2>&1 + @echo "==> Running reproducibility report" + @$(MAKE) reproducibility-report > "$(RELEASE_EVIDENCE_DIR)/reproducibility.log" 2>&1 + @rm -rf "$(RELEASE_EVIDENCE_DIR)/reproducibility" + @cp -R "$(DIST_DIR)/reproducibility" "$(RELEASE_EVIDENCE_DIR)/reproducibility" + @echo "==> Running target evidence selftest" + @$(MAKE) target-evidence-selftest > "$(RELEASE_EVIDENCE_DIR)/target-evidence-selftest.log" 2>&1 + @echo "==> Recording target Proton Bridge evidence" + @$(MAKE) target-evidence > "$(RELEASE_EVIDENCE_DIR)/target-evidence.log" 2>&1 + @rm -rf "$(RELEASE_EVIDENCE_DIR)/target-evidence" + @cp -R "$(TARGET_EVIDENCE_DIR)" "$(RELEASE_EVIDENCE_DIR)/target-evidence" + @echo "==> Generating SBOM" + @$(MAKE) sbom > "$(RELEASE_EVIDENCE_DIR)/sbom.log" 2>&1 + @rm -rf "$(RELEASE_EVIDENCE_DIR)/sbom" + @cp -R "$(DIST_DIR)/sbom" "$(RELEASE_EVIDENCE_DIR)/sbom" + @find bin protonmail test docs support scripts .github .jerboa/security.json -type f -print | LC_ALL=C sort | xargs shasum -a 256 > "$(RELEASE_EVIDENCE_DIR)/source-hashes.sha256" + @echo "Release evidence written to $(RELEASE_EVIDENCE_DIR)" + clean: rm -f protonmail-read protonmail-read-bin - rm -rf exports cache tmp + rm -rf exports cache tmp dist --- a/README.md +++ b/README.md @@ -37,14 +37,18 @@ Live Proton Bridge checks are attempted only when Bridge credentials are set. make run ARGS='--help' make doctor make test +make release-evidence make integration-test bin/protonmail-read doctor ``` -By default the Makefile expects Jerboa at `../jerboa`. Override with: +By default the Makefile uses `../jerboa/dist/jerbuild`, a local +`.jerboa/bin/jerbuild`, or a `jerbuild` on `PATH`. If no toolchain exists, +`make test` fetches the pinned Jerboa release into `.jerboa/bin`. +Override with: ```sh -make JERBOA_HOME=/path/to/jerboa test +make JERBUILD=/path/to/jerbuild test ``` The Bridge transport uses `../jerboa-ssl` by default. Override with: @@ -95,3 +99,12 @@ messages as read. The tool does not issue `STORE`, `EXPUNGE`, `COPY`, `MOVE`, Exported `.eml` files contain plaintext mail after Bridge has decrypted it locally. Treat them as sensitive. + +Credential and plaintext-mail handling are documented in +[`docs/credential-handling.md`](docs/credential-handling.md). Dependency +provenance is documented in +[`docs/dependency-provenance.md`](docs/dependency-provenance.md), and local +release evidence is documented in +[`docs/release-evidence.md`](docs/release-evidence.md). Production release +requires `make release-evidence` plus live Bridge credential and plaintext +export review. new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,51 @@ +# Security Policy + +`jerboa-protonmail` is a read-only Proton Mail Bridge IMAP client. Proton +Bridge handles Proton account authentication, mailbox decryption, and local IMAP +service exposure; this repository must treat Bridge credentials and decrypted +messages as sensitive local secrets. + +## Supported Status + +No public production-support commitment exists yet. Treat current builds as +experimental until the release checklist in `~/Release-plan.md` and +`~/mine/jerboa-production-readiness.md` is complete for this repository. + +## Security Expectations + +- Use Proton Bridge generated IMAP credentials, not Proton account passwords. +- Keep Bridge bound to loopback. Do not expose the Bridge IMAP listener on a + shared or remote interface. +- Do not commit Bridge usernames, Bridge passwords, `.env` files, exported + `.eml`/`.mbox` messages, mailbox metadata, private hostnames, or user data. +- Treat raw messages, decoded message bodies, headers, attachment filenames, + and exported `.eml` files as plaintext sensitive data after Bridge decrypts + them locally. +- The client must remain read-only by default. Release builds must not add IMAP + mutation commands without a threat model and tests. +- Diagnostics must redact credentials and avoid printing message bodies unless + the user explicitly requested `show`, `raw`, or `export-eml`. +- Production release requires marker-complete live Proton Bridge target + evidence through `JPROTONMAIL_TARGET_PROOF_FILE`; setting + `JPROTONMAIL_REQUIRE_TARGET_PROOF=1` makes missing or incomplete proof fail + closed. +- Target proof files must contain only status markers and sanitized reviewer + notes. Do not preserve Proton account passwords, Bridge passwords, raw IMAP + `LOGIN` commands, `.eml`/`.mbox` exports, message bodies, attachment content, + mailbox transcripts, private hostnames, or user data. +- Release builds must pass `make release-evidence`, which runs tests, CLI help + smoke, high-confidence secret scan, sibling `jerboa-ssl` and `jerboa-mail` + release-evidence gates, SBOM/dependency capture, and launcher/source + reproducibility checks. + +Credential and plaintext-mail details are documented in +[`docs/credential-handling.md`](docs/credential-handling.md). +Dependency provenance and local release evidence are documented in +[`docs/dependency-provenance.md`](docs/dependency-provenance.md) and +[`docs/release-evidence.md`](docs/release-evidence.md). + +## 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. --- a/bin/protonmail-read +++ b/bin/protonmail-read @@ -2,12 +2,27 @@ set -eu repo_dir=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -jerboa_home=${JERBOA_HOME:-"$repo_dir/../jerboa"} jerboa_ssl_dir=${JERBOA_SSL_DIR:-"$repo_dir/../jerboa-ssl"} jerboa_mail_dir=${JERBOA_MAIL_DIR:-"$repo_dir/../jerboa-mail"} -scheme=${SCHEME:-"$jerboa_home/.chez/bin/scheme"} + +if [ "${JERBUILD:-}" ]; then + jerbuild=$JERBUILD +elif [ -x "$repo_dir/.jerboa/bin/jerbuild" ]; then + jerbuild="$repo_dir/.jerboa/bin/jerbuild" +elif [ -x "$repo_dir/../jerboa/dist/jerbuild" ]; then + jerbuild="$repo_dir/../jerboa/dist/jerbuild" +elif [ -x "$repo_dir/../jerboa/jerbuild" ]; then + jerbuild="$repo_dir/../jerboa/jerbuild" +elif command -v jerbuild >/dev/null 2>&1; then + jerbuild=$(command -v jerbuild) +else + echo "ERROR: jerbuild not found; run make test or set JERBUILD=/path/to/jerbuild" >&2 + exit 1 +fi + +jerboa_home=$("$jerbuild" --jerboa-home) export JERBOA_SSL_LIB=${JERBOA_SSL_LIB:-"$jerboa_ssl_dir"} -exec "$scheme" -q --libdirs "$repo_dir:$jerboa_mail_dir:$jerboa_ssl_dir/lib:$jerboa_home/lib" \ - --script "$repo_dir/main.ss" -- "$@" +exec "$jerbuild" exec --libdirs "$repo_dir:$jerboa_mail_dir:$jerboa_ssl_dir/lib:$jerboa_home/lib" \ + "$repo_dir/main.ss" -- "$@" new file mode 100644 --- /dev/null +++ b/docs/credential-handling.md @@ -0,0 +1,70 @@ +# Credential Handling + +`jerboa-protonmail` talks to a local Proton Mail Bridge IMAP listener. Bridge +terminates Proton authentication and decrypts mailbox content locally, so this +tool only needs Bridge IMAP settings. + +## Credential Sources + +The CLI reads Bridge configuration from environment variables: + +```sh +PROTON_BRIDGE_HOST=127.0.0.1 +PROTON_BRIDGE_PORT=1143 +PROTON_BRIDGE_USER=bridge-generated-user +PROTON_BRIDGE_PASSWORD=bridge-generated-password +PROTON_BRIDGE_TLS=auto +``` + +Use Bridge generated IMAP credentials only. Do not use a Proton account +password. Prefer a shell session, process supervisor secret injection, or a +local file loaded by the shell with restrictive permissions: + +```sh +chmod 0600 .protonmail-reader.local +``` + +If a future config-file loader is added, it must reject group-readable or +world-readable files when they contain a password. + +## Logging And Output + +The `doctor` command redacts the Bridge username and password. Error messages +must not include raw IMAP `LOGIN` commands, Bridge passwords, or complete +mailbox responses that may contain message content. + +The `show`, `raw`, and `export-eml` commands intentionally disclose decrypted +mail selected by the user. Treat their output as sensitive: + +- stdout may be captured by shell history wrappers, terminal logs, or calling + programs. +- `.eml` exports are plaintext decrypted messages. +- headers can reveal correspondents, subjects, dates, and mailbox structure. +- raw IMAP literals can contain attachments and embedded secrets. + +## Read-Only Mailbox Policy + +Production releases must stay read-only unless a separate threat model and test +suite is added. Allowed fetches use `BODY.PEEK[...]` and read/search/list +commands. The client must not issue `STORE`, `EXPUNGE`, `COPY`, `MOVE`, +`APPEND`, `CREATE`, `DELETE`, or other mailbox-mutating commands by default. + +## Release Requirements + +Before a production release: + +- Run `make release-evidence` from a clean checkout. +- Run release evidence with `JPROTONMAIL_REQUIRE_TARGET_PROOF=1` and a + marker-complete `JPROTONMAIL_TARGET_PROOF_FILE` from a least-privilege live + Proton Bridge setup. +- The target proof must confirm Bridge doctor smoke, loopback-only exposure, + generated Bridge credentials, credential redaction, read-only IMAP behavior, + `BODY.PEEK` policy, plaintext export review, target-platform smoke, + production mail review, no raw mail output retention, and the + sensitive-artifact policy. +- Run the baseline tracked-file secret scan and a history scan for Bridge + credentials or exported mail. +- Confirm release artifacts do not include `.eml`, `.mbox`, cache, export, + transcript, or local config files. +- Review `jerboa-ssl` TLS shim and OpenSSL dependency versions. +- Review `jerboa-mail` parsing behavior for untrusted MIME/header inputs. new file mode 100644 --- /dev/null +++ b/docs/dependency-provenance.md @@ -0,0 +1,19 @@ +# Dependency Provenance + +`jerboa-protonmail` is a read-only Proton Mail Bridge IMAP client. It does not +implement Proton account authentication or mailbox decryption directly. + +Dependency boundaries: + +- Proton Mail Bridge performs Proton authentication and local message + decryption. +- `jerboa-ssl` provides the local TLS transport when TLS is enabled. +- `jerboa-mail` parses and decodes headers, MIME structure, and friendly message + output. +- This repository reads Bridge settings from environment variables and does not + persist credentials. + +`make release-evidence` runs dependency release-evidence gates for the selected +`jerboa-ssl` and `jerboa-mail` checkouts, then records their git state, hashes, +OpenSSL advisory evidence, TLS reproducibility result, and mail parser +dependency audit evidence in the local SBOM bundle. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,61 @@ +# Release Evidence + +`make release-evidence` writes ignored local evidence under +`dist/release-evidence/`. + +The evidence bundle contains: + +- high-confidence tracked-file secret-scan output; +- smoke-test output; +- CLI help smoke output; +- sibling `jerboa-ssl` and `jerboa-mail` release-evidence logs; +- SBOM-style source, workflow, launcher, dependency, and toolchain manifests; +- copied `jerboa-ssl` OpenSSL advisory and reproducibility evidence when + available; +- copied `jerboa-mail` dependency audit evidence when available; +- target Proton Bridge proof status and proof-gate self-test output; +- a reproducibility note that this repo ships a launcher/script path rather than + a standalone native binary. + +This local evidence does not replace live Proton Bridge release blockers: +marker-complete least-privilege Bridge credential proof, loopback-only Bridge +exposure review, plaintext export review, and external review of the read-only +IMAP command set are still required before production support claims. + +## Target Proton Bridge Proof + +Local `make release-evidence` always writes +`dist/release-evidence/target-evidence/status.txt`. Without a proof file, the +target-only Bridge checks are recorded as `blocked-not-run` so local smoke +evidence is not mistaken for live mailbox evidence. + +To attach production target evidence, create a sanitized marker-only proof file +and run: + +```sh +JPROTONMAIL_TARGET_PROOF_FILE=/path/to/protonmail-target-proof.txt \ +JPROTONMAIL_REQUIRE_TARGET_PROOF=1 \ +make release-evidence +``` + +The proof file must contain every line below exactly: + +```text +bridge_doctor_status=target-evidence-recorded +bridge_loopback_status=confirmed +bridge_generated_credential_status=confirmed +bridge_credential_redaction_status=target-evidence-recorded +read_only_imap_status=target-evidence-recorded +body_peek_policy_status=confirmed +plaintext_export_review_status=target-evidence-recorded +target_platform_smoke_status=target-evidence-recorded +production_mail_review_status=current-run-recorded +raw_mail_output_status=not-preserved +sensitive_artifact_policy=no-proton-account-passwords-bridge-passwords-eml-mbox-mail-bodies-attachments-or-imap-transcripts-preserved +``` + +The proof gate rejects missing, empty, marker-incomplete, or high-confidence +sensitive proof files. The proof must not preserve Proton account passwords, +Bridge passwords, raw IMAP `LOGIN` commands, `.eml`/`.mbox` files, decrypted +message bodies, attachments, mailbox transcripts, private hostnames, or raw +Bridge output. --- a/main.ss +++ b/main.ss @@ -1,31 +1,6 @@ #!chezscheme ;;; Script entry point for protonmail-read. -(import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition - make-date make-time)) - -(define home (or (getenv "HOME") ".")) -(define jerboa-dir - (or (getenv "JERBOA_HOME") - (string-append home "/mine/jerboa"))) -(define project-dir - (or (getenv "JERBOA_PROTONMAIL_DIR") - (current-directory))) - -(library-directories - (append - (list (cons project-dir project-dir) - (cons (string-append jerboa-dir "/lib") - (string-append jerboa-dir "/lib"))) - (library-directories))) - (import (protonmail cli)) (run-cli (command-line-arguments)) --- a/protonmail/cli.ss +++ b/protonmail/cli.ss @@ -4,15 +4,7 @@ (library (protonmail cli) (export run-cli usage-string) - (import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition - make-date make-time) + (import (rnrs) (protonmail config) (protonmail imap client) (jerboa-mail encoding) @@ -177,7 +169,7 @@ (print-summary-table summaries)))) (define (write-stdout-bytes bv) - (let ([p (standard-output-port (buffer-mode block))]) + (let ([p (standard-output-port)]) (put-bytevector p bv) (flush-output-port p))) --- a/protonmail/config.ss +++ b/protonmail/config.ss @@ -14,15 +14,8 @@ config-errors redact-secret) - (import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition - make-date make-time)) + (import (rnrs) + (only (jerboa core) getenv)) (define (make-config host port user password tls) (vector host port user password tls)) --- a/protonmail/imap/client.ss +++ b/protonmail/imap/client.ss @@ -11,15 +11,7 @@ imap-quote imap-final-ok?) - (import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition - make-date make-time) + (import (rnrs) (protonmail config) (protonmail imap parser) (protonmail imap transport) @@ -49,17 +41,17 @@ (string-append "A" (number->string n)))) (define (imap-quote s) - (let ([out (open-output-string)]) - (write-char #\" out) - (let loop ([i 0]) - (when (< i (string-length s)) - (let ([ch (string-ref s i)]) - (when (or (char=? ch #\\) (char=? ch #\")) - (write-char #\\ out)) - (write-char ch out) - (loop (+ i 1))))) - (write-char #\" out) - (get-output-string out))) + (call-with-string-output-port + (lambda (out) + (write-char #\" out) + (let loop ([i 0]) + (when (< i (string-length s)) + (let ([ch (string-ref s i)]) + (when (or (char=? ch #\\) (char=? ch #\")) + (write-char #\\ out)) + (write-char ch out) + (loop (+ i 1))))) + (write-char #\" out)))) (define (imap-command c command) (map imap-entry-line (imap-command-entries c command))) @@ -162,24 +154,25 @@ (define (imap-with-session cfg proc) (let ([client #f]) - (guard (e [#t - (when client - (safe-close-client client)) - (raise e)]) - (set! client (open-client cfg)) - (require-ok 'imap-capability - (imap-command client "CAPABILITY")) - (require-ok 'imap-login - (imap-command client - (string-append - "LOGIN " - (imap-quote (config-user cfg)) - " " - (imap-quote (config-password cfg))))) - (let ([result (proc client)]) - (safe-logout client) - (safe-close-client client) - result)))) + (dynamic-wind + (lambda () #t) + (lambda () + (set! client (open-client cfg)) + (require-ok 'imap-capability + (imap-command client "CAPABILITY")) + (require-ok 'imap-login + (imap-command client + (string-append + "LOGIN " + (imap-quote (config-user cfg)) + " " + (imap-quote (config-password cfg))))) + (let ([result (proc client)]) + (safe-logout client) + result)) + (lambda () + (when client + (safe-close-client client)))))) (define (nth xs n default) (cond --- a/protonmail/imap/parser.ss +++ b/protonmail/imap/parser.ss @@ -16,15 +16,7 @@ imap-literal-marker? imap-literal-marker-length) - (import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition