Harden browser release and credential handling
ober
ddb100751e7fd010b2e0b3f71332d2d015eef030
--- a/.build.yml +++ b/.build.yml @@ -118,12 +118,10 @@ tasks: # Offline Qt GUI / snapshot harness (offscreen, headless). Kept hermetic # in CI (JWB_TEST_NO_NETWORK) so it does not depend on live example.com; # the full networked path runs locally and on the amd64 verifier. - # --no-sandbox: Chromium's zygote can't sandbox inside the CI container. # Exported env (not make vars): the Makefile's `?=` defaults defer to env, # and JERBOA_BROWSER_LIB must reach the scheme process directly. export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-gui @@ -131,8 +129,7 @@ tasks: cd jerboa-browser # Stage 1 buffer-model functional tests (offscreen, hermetic in CI). export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-buffers @@ -151,8 +148,7 @@ tasks: # Stage 2 keymap+minibuffer+command controller functional tests # (offscreen, hermetic): synthetic key tokens drive real buffer state. export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-commands @@ -162,8 +158,7 @@ tasks: # the app-wide event filter → poll queue → emacs keymap/command dispatch # (offscreen, hermetic). Proves the GUI's key path, not just the controller. export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-keys @@ -173,8 +168,7 @@ tasks: # the command controller against rendered data: pages (offscreen, hermetic # — data: bypasses the no-network policy; find needs a rendered frame). export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-nav @@ -185,8 +179,7 @@ tasks: # hermetic — data: bypasses the no-network policy; the overlay reads # getBoundingClientRect, so the harness shows + sizes + pumps first). export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-hint @@ -196,8 +189,7 @@ tasks: # masked prompt, save/fill/forget autofill against a rendered data: form # (offscreen, hermetic). The vault touches no env/config/disk by design. export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-pass @@ -208,8 +200,7 @@ tasks: # rendered offline into a new buffer (offscreen, hermetic). --disable-gpu # is required: the bindings page would otherwise FATAL the GPU surface. export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-polish @@ -222,8 +213,7 @@ tasks: # session also builds the engine from the built-in starter list. The # engine_match semantics are covered by the C++ ctest in the verify task. export QT_QPA_PLATFORM=offscreen JWB_TEST_NO_NETWORK=1 - export QTWEBENGINE_DISABLE_SANDBOX=1 - export QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" + export QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu" export JERBOA_HOME="$HOME/jerboa" SCHEME="$(command -v scheme)" export JERBOA_BROWSER_LIB="$PWD/qt-webengine/build/libjerboa_browser.so" make test-adblock new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +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 + QT_QPA_PLATFORM: offscreen + QTWEBENGINE_CHROMIUM_FLAGS: --disable-gpu + JWB_TEST_NO_NETWORK: "1" + +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 cmake pkg-config \ + qt6-base-dev qt6-webengine-dev xvfb + 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: Build Qt WebEngine backend + run: | + cmake -S qt-webengine -B qt-webengine/build -DCMAKE_BUILD_TYPE=Release + cmake --build qt-webengine/build --parallel 2 + + - 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 @@ -7,6 +7,8 @@ build/ /jerboa-browser jerboa-browser-static-qt-linux-amd64.tar.gz .bcache/ +.jerboa/bin/ +dist/ *.so *.wpo *.boot new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "repo": "jerboa-browser", + "extends": ["jerboa:gui", "jerboa:network-client", "jerboa:crypto", "jerboa:ffi"], + "paths": { + "production": ["scheme/**/*.ss", "qt-webengine/src/**/*.{cpp,h}", "include/**/*.h", "build-binary.ss", "packaging/**/*.sh", "support/**/*.sh", "Makefile"], + "tests": ["scheme/**/*-test.ss", "qt-webengine/tests/**"], + "generated": ["build/**", "dist/**", "target/**", "qt-webengine/build/**", "test-artifacts/**", "*.so", "*.dylib", "*.wpo", "*.boot"], + "vendor": ["vendor/**", "qt-webengine/vendor/**"], + "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"] + }, + "policy": { + "failOn": ["critical", "high"], + "imports": { "directChezscheme": "deny" }, + "ffi": { "allowed": true, "requireLazyLoading": true, "requireBoundaryDocs": true }, + "browser": { + "chromiumSandbox": "required", + "defaultProfile": "off-record", + "persistentProfileRequiresEncryptedVault": true, + "envPassphrase": "deny" + }, + "process": { "shellInterpolation": "deny" }, + "network": { "denyByDefaultCapabilities": true }, + "secrets": { "forbidLogging": true, "ramOnlyPasswordVault": true, "strictStateFilePermissions": true } + }, + "suppressions": [] +} --- a/.jerbuild +++ b/.jerbuild @@ -8,4 +8,3 @@ (output "jerboa-browser") (libdirs "vendor/jerboa-yubikey/lib" "scheme" "vendor/jerboa-fuse/lib") (pre-build "sh support/ensure-vendor.sh jerboa-yubikey https://git.sr.ht/~lisp/jerboa-yubikey") -(pre-build "sh support/gen-browser-vault-secret.sh scheme/browser/build-vault-secret.ss") 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,17 +1,27 @@ # jerbuild bundles Chez Scheme + the jerboa stdlib, so building the browser -# entry needs only `jerbuild` + a C compiler — no jerboa source checkout. (The -# full Qt/WebKit GUI bundle additionally needs the native wrapper.) -JERBUILD ?= jerbuild -JH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null) +# entry needs only `jerbuild` + a C compiler. CI uses a pinned project-local +# release toolchain; developer machines can still override JERBUILD explicitly. +JERBOA_VERSION ?= v0.2.3 +JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin +JERBUILD ?= $(shell if [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \ + printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; \ +elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/dev/null 2>&1; then \ + command -v jerbuild; \ +else \ + printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; \ +fi) +JH := $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null) JERBOA_YUBIKEY_URL ?= https://git.sr.ht/~lisp/jerboa-yubikey JERBOA_YUBIKEY_DIR := vendor/jerboa-yubikey JERBOA_YUBIKEY_LIB := $(JERBOA_YUBIKEY_DIR)/lib JEXEC := $(JERBUILD) exec --libdirs $(CURDIR)/$(JERBOA_YUBIKEY_LIB):$(CURDIR)/scheme:$(CURDIR)/vendor/jerboa-fuse/lib:$(JH)/lib -JERBUILD_ERROR := "ERROR: jerbuild not found on PATH (or '$(JERBUILD) --jerboa-home' failed). Install jerbuild, or set JERBUILD=/path/to/jerbuild." +JERBUILD_ERROR := "ERROR: jerbuild unavailable (or '$(JERBUILD) --jerboa-home' failed). Run make ensure-jerboa-tools, install jerbuild, or set JERBUILD=/path/to/jerbuild." BIN_DIR ?= $(HOME)/.local/bin +DIST_DIR ?= dist/release-evidence CC ?= cc CARGO ?= cargo +CARGO_AUDIT ?= $(shell command -v cargo-audit 2>/dev/null || printf '%s/.cargo/bin/cargo-audit' "$$HOME") DOCKER ?= docker DOCKER_PLATFORM ?= linux/amd64 DOCKER_BUILD_FLAGS ?= @@ -31,7 +41,6 @@ STATIC_QT_TARBALL ?= jerboa-browser-static-qt-linux-amd64.tar.gz SOEXT := $(if $(filter Darwin,$(shell uname -s)),dylib,so) FUSE_SHARED_FLAG := $(if $(filter Darwin,$(shell uname -s)),-dynamiclib,-shared) FUSE_SHIM := libjerboa_fuse_mount.so -BUILD_VAULT_SECRET := scheme/browser/build-vault-secret.ss YUBIKEY_NATIVE_SHARED := $(JERBOA_YUBIKEY_DIR)/yubikey-native/target/release/libyubikey_native.$(SOEXT) # `make binary` links the static archive from a temp dir and leaves no shared # lib behind, so build the cdylib from jerbuild's bundled crate if it isn't @@ -50,9 +59,6 @@ lib/libjerboa_native.$(SOEXT): | check-jerbuild $(FUSE_SHIM): vendor/jerboa-fuse/src/mount_helper.c $(CC) $(FUSE_SHARED_FLAG) -fPIC -O2 -o $@ $< -$(BUILD_VAULT_SECRET): - sh support/gen-browser-vault-secret.sh $@ - vendor-yubikey: sh support/ensure-vendor.sh jerboa-yubikey "$(JERBOA_YUBIKEY_URL)" @test -f "$(JERBOA_YUBIKEY_LIB)/yubikey/auth.sls" || \ @@ -65,7 +71,7 @@ lib/libyubikey_native.$(SOEXT): vendor-yubikey yubikey-native: lib/libyubikey_native.$(SOEXT) -.PHONY: all build binary install run test test-keymap test-minibuffer test-commands test-keys test-nav test-hint test-pass test-polish test-securestore test-adblock test-gui test-buffers repl clean help check-jerbuild check-docker static-qt docker-static-qt vault-secret rotate-vault-secret vendor-yubikey yubikey-native +.PHONY: all build binary install run test test-all test-keymap test-minibuffer test-commands test-keys test-nav test-hint test-pass test-polish test-securestore test-adblock test-gui test-buffers test-native repl security audit verify release-evidence clean help check-jerbuild check-docker ensure-jerboa-tools static-qt docker-static-qt vendor-yubikey yubikey-native .DEFAULT_GOAL := help all: binary @@ -73,12 +79,26 @@ all: binary check-jerbuild: @test -n "$(JH)" || { echo $(JERBUILD_ERROR) >&2; exit 1; } +ensure-jerboa-tools: + @if [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && [ -x "$(JERBOA_TOOL_DIR)/jerboa" ]; then \ + echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \ + elif command -v jerbuild >/dev/null 2>&1 && command -v jerboa >/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>"; \ + exit 1; \ + } + check-docker: @command -v $(DOCKER) >/dev/null 2>&1 || \ { echo "ERROR: Docker executable '$(DOCKER)' not found. Install Docker or set DOCKER=/path/to/docker."; exit 1; } # Build the self-contained native ./jerboa-browser via .jerbuild. -binary: check-jerbuild vendor-yubikey $(FUSE_SHIM) $(BUILD_VAULT_SECRET) +binary: check-jerbuild vendor-yubikey $(FUSE_SHIM) $(JERBUILD) build @echo "" && ls -lh jerboa-browser && file jerboa-browser @@ -100,7 +120,10 @@ test test-keymap test-minibuffer test-gui test-buffers repl: check-jerbuild vend # Run the (browser) test suite in the interpreter (no binary needed). Depends on # the pure secure-store unit tests so they run as part of `make test`. test: test-securestore - $(JEXEC) scheme/browser-test.ss + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ + $(JEXEC) scheme/browser-test.ss + +test-all: test test-keymap test-minibuffer test-commands test-keys test-nav test-hint test-pass test-polish test-adblock test-gui test-buffers test-native # Stage 8 secure-store unit tests: bookmark (de)serialize, AES-256-GCM seal/open, # RSA-OAEP wrap + simulated card unwrap, tamper detection, masked-DK zeroing. @@ -118,65 +141,129 @@ test-minibuffer: # Stage 2 keymap+minibuffer+command controller functional tests (offscreen). test-commands: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-commands-test.ss # Stage 2 end-to-end Qt key-capture test: real synthetic Qt key events flow # through the app-wide filter → poll queue → controller (offscreen, headless). test-keys: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-keys-test.ss # Stage 3 navigation-parity functional tests: scroll/zoom/find/copy driven # through the command controller against rendered data: pages (offscreen). test-nav: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-nav-test.ss # Stage 4 hint-mode functional tests: JS link-hint overlay + follow (this/new # buffer) driven through the command controller against rendered data: pages. test-hint: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-hint-test.ss # Stage 5 password-manager functional tests: in-RAM vault round-trip + zeroing, # masked prompt, save/fill/forget autofill against a rendered data: form. test-pass: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-pass-test.ss # Stage 6 polish functional tests: in-RAM bookmarks + commands, M-x docs, the # status modes + echo-area wiring, and the describe-bindings help page. test-polish: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-polish-test.ss # Stage 7 ad/tracker blocking (adblock-rust): the process-global toggle ABI and # the toggle-adblock command + echo. The engine_match semantics are unit-tested # in C++ (cd qt-webengine/build && ctest -R adblock). test-adblock: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-adblock-test.ss # Offline Qt GUI / snapshot harness (headless). Writes PNGs to ./test-artifacts. test-gui: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-gui-test.ss # Stage 1 buffer-model functional tests (headless). Writes PNGs to ./test-artifacts. test-buffers: - QT_QPA_PLATFORM=offscreen \ + QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu \ $(JEXEC) scheme/browser-buffers-test.ss +test-native: + @if [ -d qt-webengine/build ] && command -v ctest >/dev/null 2>&1; then \ + ctest --test-dir qt-webengine/build --output-on-failure; \ + else \ + echo "native Qt/WebEngine ctest skipped: qt-webengine/build or ctest is unavailable"; \ + fi + +security: + sh scripts/security-check.sh + +audit: + @mkdir -p "$(DIST_DIR)" + @if [ -x "$(CARGO_AUDIT)" ] && [ -f qt-webengine/vendor/adblock-rust-ffi/Cargo.toml ]; then \ + cd qt-webengine/vendor/adblock-rust-ffi && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-adblock-ffi.txt"; \ + else \ + echo "cargo-audit not installed or adblock Cargo.toml missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-adblock-ffi.txt"; \ + fi + @if [ -x "$(CARGO_AUDIT)" ] && [ -f "$(JERBOA_YUBIKEY_DIR)/yubikey-native/Cargo.toml" ]; then \ + cd "$(JERBOA_YUBIKEY_DIR)/yubikey-native" && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-yubikey-native.txt"; \ + else \ + echo "cargo-audit not installed or YubiKey native Cargo.toml missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-yubikey-native.txt"; \ + fi + +verify: test-all security audit + +release-evidence: verify binary + @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; \ + command -v cmake >/dev/null 2>&1 && cmake --version | head -1 || true; \ + command -v "$(CARGO)" >/dev/null 2>&1 && "$(CARGO)" --version || true; \ + } > "$(DIST_DIR)/build-environment.txt" + @QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu ./jerboa-browser version > "$(DIST_DIR)/browser-version.txt" 2>&1 || true + @find scheme qt-webengine/src qt-webengine/tests include support packaging -type f \( -name '*.ss' -o -name '*.sls' -o -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.h' -o -name '*.sh' -o -name '*.md' -o -name 'CMakeLists.txt' \) -print | sort | xargs shasum -a 256 > "$(DIST_DIR)/source-sha256.txt" + @find qt-webengine/vendor/adblock-rust-ffi "$(JERBOA_YUBIKEY_DIR)/yubikey-native" -maxdepth 2 -type f \( -name Cargo.toml -o -name Cargo.lock \) -print 2>/dev/null | sort | xargs shasum -a 256 > "$(DIST_DIR)/native-cargo-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/webengine-sandbox.md docs/release-evidence.md scripts/security-check.sh > "$(DIST_DIR)/release-inputs-sha256.txt" + @shasum -a 256 jerboa-browser > "$(DIST_DIR)/binary-sha256.txt" + @if command -v otool >/dev/null 2>&1; then \ + { otool -L jerboa-browser 2>/dev/null || true; otool -L qt-webengine/build/libjerboa_browser.* 2>/dev/null || true; otool -L lib/libyubikey_native.* 2>/dev/null || true; } > "$(DIST_DIR)/native-linkage.txt"; \ + elif command -v ldd >/dev/null 2>&1; then \ + { ldd jerboa-browser 2>/dev/null || true; ldd qt-webengine/build/libjerboa_browser.* 2>/dev/null || true; ldd lib/libyubikey_native.* 2>/dev/null || true; } > "$(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 qt-webengine/vendor/adblock-rust-ffi/Cargo.toml ]; then \ + "$(CARGO)" metadata --format-version 1 --manifest-path qt-webengine/vendor/adblock-rust-ffi/Cargo.toml > "$(DIST_DIR)/cargo-metadata-adblock-ffi.json"; \ + fi + @if command -v "$(CARGO)" >/dev/null 2>&1 && [ -f "$(JERBOA_YUBIKEY_DIR)/yubikey-native/Cargo.toml" ]; then \ + "$(CARGO)" metadata --format-version 1 --manifest-path "$(JERBOA_YUBIKEY_DIR)/yubikey-native/Cargo.toml" > "$(DIST_DIR)/cargo-metadata-yubikey-native.json"; \ + fi + @if [ -x "$(CARGO_AUDIT)" ] && [ -f qt-webengine/vendor/adblock-rust-ffi/Cargo.toml ]; then \ + cd qt-webengine/vendor/adblock-rust-ffi && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-adblock-ffi.txt"; \ + else \ + echo "cargo-audit not installed or adblock Cargo.toml missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-adblock-ffi.txt"; \ + fi + @if [ -x "$(CARGO_AUDIT)" ] && [ -f "$(JERBOA_YUBIKEY_DIR)/yubikey-native/Cargo.toml" ]; then \ + cd "$(JERBOA_YUBIKEY_DIR)/yubikey-native" && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-yubikey-native.txt"; \ + else \ + echo "cargo-audit not installed or YubiKey native Cargo.toml missing; release owner must review before publication." > "$(DIST_DIR)/rustsec-yubikey-native.txt"; \ + fi + @echo "release evidence written to $(DIST_DIR)" + # REPL with (browser) preloaded, in the interpreter. repl: $(JEXEC) scheme/browser-repl.ss -vault-secret: $(BUILD_VAULT_SECRET) - -rotate-vault-secret: - sh support/gen-browser-vault-secret.sh --force $(BUILD_VAULT_SECRET) - # Docker wrapper matching the jerboa-emacs static-qt workflow. Qt WebEngine is # not emitted as one fully static executable; the artifact is a bundled Linux # tarball with the native browser binary plus the Qt WebEngine runtime. @@ -225,7 +312,7 @@ clean: rm -f $(FUSE_SHIM) rm -f lib/libyubikey_native.$(SOEXT) rm -f $(STATIC_QT_TARBALL) - rm -rf .bcache test-artifacts + rm -rf .bcache test-artifacts dist find scheme -name '*.so' -delete 2>/dev/null || true find scheme -name '*.wpo' -delete 2>/dev/null || true @@ -235,8 +322,8 @@ help: @echo " make binary build the self-contained native ./jerboa-browser" @echo " make install build + install to $(BIN_DIR)/jerboa-browser" @echo " make static-qt build the bundled Linux Qt/WebEngine tarball via Docker" - @echo " make vault-secret create the local build vault secret, if missing" - @echo " make rotate-vault-secret replace the local build vault secret" + @echo " make verify run full tests, security checks, and dependency audit" + @echo " make release-evidence write release evidence under dist/release-evidence" @echo " make vendor-yubikey clone jerboa-yubikey into vendor/" @echo " make yubikey-native build lib/libyubikey_native for direct USB" @echo " make test run the (browser) test suite (interpreter)" --- a/README.md +++ b/README.md @@ -133,16 +133,11 @@ Explicit overrides are still available: - `JERBOA_BROWSER_VAULT_PASSPHRASE_FILE` — 0600 file containing a recovery or test passphrase. -- `JERBOA_BROWSER_VAULT_PASSPHRASE` — local testing only. - `JERBOA_BROWSER_VAULT_UNLOCK=prompt` — terminal passphrase prompt. -- `JERBOA_BROWSER_VAULT_UNLOCK=build-secret` — development fallback. -The build-secret fallback is not production-grade: `make binary` creates -`scheme/browser/build-vault-secret.ss` if it is missing, and that generated, -ignored module holds a random build-local passphrase as masked bytes rather -than a printable string. Normal rebuilds preserve it; `make rotate-vault-secret` -replaces it. Anyone with the generated source or binary can still recover that -secret, so use it only for development or migration. +The browser intentionally does not accept a vault passphrase through environment +variables and does not embed a build-local fallback secret. Use the YubiKey PIV +default, a protected passphrase file, or the interactive prompt. ## Build / test @@ -152,6 +147,8 @@ make static-qt # bundled Linux Qt/WebEngine tarball via Docker (linux/amd make vendor-yubikey # clone https://git.sr.ht/~lisp/jerboa-yubikey into vendor/ make yubikey-native # build lib/libyubikey_native for direct USB YubiKey access make test # (browser) suite + secure-store unit tests; also test-{keymap,…,polish} +make verify # full test set, security checks, and dependency audit evidence +make release-evidence # write dist/release-evidence/ for release review make test-securestore # just the pure secure-store crypto/format unit tests make test-gui # offline Qt snapshot harness (offscreen, headless) ``` new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,43 @@ +# Security Policy + +`jerboa-browser` is security-sensitive. It embeds Qt WebEngine, exposes native +FFI, handles website credentials, and can persist browser profile data. Treat it +as private/experimental until the release gates below are complete. + +## Supported Status + +No public production-support commitment exists yet. A public production release +requires: + +- `make verify` passes on a clean checkout. +- `make release-evidence` produces a complete `dist/release-evidence/` bundle. +- `jerboa_security_scan` reports no medium-or-higher findings in non-vendored + production source. +- The Qt WebEngine backend builds and its native tests pass on every supported + target OS. +- RustSec/native dependency audits are clean or documented as accepted release + risks. +- Target-OS evidence confirms the Chromium/Qt WebEngine sandbox is enabled. + +## Security Expectations + +- Web content is hostile. Do not grant network, downloads, clipboard, file, or + persistent-storage capabilities unless the caller explicitly requested them. +- The default WebEngine profile must remain off-record. Persistent cookies, + cache, local storage, and downloads require the browser-owned encrypted vault. +- Do not accept browser-profile vault secrets through process environment + variables and do not embed build-local fallback secrets. +- Website passwords stay RAM-only and must not be written to profile storage, + config files, logs, command lines, or environment variables. +- Keep Qt WebEngine's sandbox enabled. Do not set `QTWEBENGINE_DISABLE_SANDBOX` + in code, packaging, docs, or CI. +- Keep application Scheme source on `(scheme)`/Jerboa imports. Direct + `(chezscheme)` imports are denied in production/test source. +- Native boundaries must be documented, length-checked, and covered by release + evidence. + +## Reporting + +Before public release, report issues privately to the repository owner. After a +public release, replace this section with a dedicated advisory contact, +supported versions, and disclosure window. --- a/build-binary.ss +++ b/build-binary.ss @@ -15,7 +15,10 @@ ;;; Compiled library objects go to ./.bcache so the read-only Jerboa tree is ;;; never written to. -(import (chezscheme)) +(import (scheme) + (only (std security taint) + safe-delete-file + safe-system)) (define repo (current-directory)) (define bcache (format "~a/.bcache" repo)) @@ -56,7 +59,48 @@ ;; --- library search path ------------------------------------------------- ;; (source . object) pairs: read .ss/.sls from the repo + Jerboa stdlib, write ;; the compiled .so under ./.bcache (NOT into the read-only Jerboa tree). -(system (format "rm -rf ~a" bcache)) +(define (build-path-safe? path) + (and (string? path) + (> (string-length path) 0) + (let loop ((i 0)) + (cond ((= i (string-length path)) #t) + ((char=? (string-ref path i) #\nul) #f) + (else (loop (+ i 1))))))) + +(define (require-build-path who path) + (unless (build-path-safe? path) + (error who "invalid build path" path)) + path) + +(define (shell-quote s) + (let ((p (open-output-string))) + (display "'" p) + (let loop ((i 0)) + (when (< i (string-length s)) + (let ((ch (string-ref s i))) + (if (char=? ch #\') + (display "'\\''" p) + (display ch p))) + (loop (+ i 1)))) + (display "'" p) + (get-output-string p))) + +(define (rm-rf path) + (let ((checked (require-build-path 'rm-rf path))) + (safe-system (format "rm -rf -- ~a" (shell-quote checked))))) + +(define (call-with-replace-output-file path proc) + (let ((p (open-file-output-port + (require-build-path 'call-with-replace-output-file path) + (file-options no-fail) + (buffer-mode block) + (native-transcoder)))) + (dynamic-wind + (lambda () (void)) + (lambda () (proc p)) + (lambda () (close-port p))))) + +(rm-rf bcache) (mkdir bcache) (library-directories (list (cons (format "~a/scheme" repo) (format "~a/scheme" bcache)) @@ -68,7 +112,7 @@ (data (get-bytevector-all p)) (n (bytevector-length data))) (close-port p) - (call-with-output-file out + (call-with-replace-output-file out (lambda (o) (fprintf o "/* Auto-generated — do not edit */\n") (fprintf o "static const unsigned char ~a[] = {\n" array) @@ -80,8 +124,7 @@ (when (= 15 (modulo i 16)) (fprintf o "\n")) (lp (+ i 1)))) (fprintf o "\n};\n") - (fprintf o "static const unsigned int ~a = ~a;\n" size-name n)) - 'replace) + (fprintf o "static const unsigned int ~a = ~a;\n" size-name n))) (printf " ~a: ~a bytes\n" out n))) ;; All compiled .so under dir (recursively) — the library closure to embed. @@ -121,7 +164,7 @@ ;; --- [3/4] generate C bootstrap, compile + link -------------------------- (printf "[3/4] Compiling + linking...\n") -(call-with-output-file "jb-main.c" +(call-with-replace-output-file "jb-main.c" (lambda (o) (fprintf o "/* Auto-generated — do not edit */\n") (fprintf o "#define _GNU_SOURCE\n#include <stdlib.h>\n#include <stdio.h>\n") @@ -158,11 +201,15 @@ (fprintf o " Sregister_boot_file_bytes(\"jerboa-browser\", (void*)jb_boot_data, jb_boot_size);\n") (fprintf o " Sbuild_heap(NULL, NULL);\n") (fprintf o " int status = Sscheme_script(prog_path, argc, (const char **)argv);\n") - (fprintf o " unlink(prog_path); Sscheme_deinit(); return status;\n}\n")) - 'replace) + (fprintf o " unlink(prog_path); Sscheme_deinit(); return status;\n}\n"))) (define cc (or (getenv "CC") "cc")) -(define (sh cmd) (printf " ~a\n" cmd) (let ((rc (system cmd))) (unless (= rc 0) (printf "Error (rc=~a)\n" rc) (exit 1)))) +(define (sh cmd) + (printf " ~a\n" cmd) + (let ((rc (safe-system cmd))) + (unless (= rc 0) + (printf "Error (rc=~a)\n" rc) + (exit 1)))) (sh (format "~a -c -O2 -I~a -o jb-main.o jb-main.c" cc chez-dir)) (if macos? @@ -181,9 +228,9 @@ ;; --- [4/4] cleanup ------------------------------------------------------- (printf "[4/4] Cleanup...\n") -(for-each (lambda (f) (when (file-exists? f) (delete-file f))) +(for-each (lambda (f) (when (file-exists? f) (safe-delete-file f))) '("jb-main.c" "jb-main.o" "jb_program.h" "jb_petite_boot.h" "jb_scheme_boot.h" "jb_boot.h" "jerboa-browser.boot" "scheme/browser-main.so")) -(system (format "rm -rf ~a" bcache)) +(rm-rf bcache) (printf "\nDone! Binary: ./jerboa-browser\n") new file mode 100644 --- /dev/null +++ b/docs/credential-handling.md @@ -0,0 +1,46 @@ +# Credential Handling + +## Website Passwords + +The password manager in `(browser passwords)` is intentionally RAM-only. It does +not write credentials to config files, environment variables, command lines, or +the browser profile. Stored secrets are XOR-masked with a per-session pad and +wiped after each `vault-with-secret` use. + +Autofill reconstitutes plaintext only long enough to inject into the selected +page form. A compromised page that receives autofill can read the filled fields, +so autofill remains an explicit user action. + +## Bookmarks + +Bookmarks may persist through `(browser secure-store)`. The store uses a random +256-bit data key with AES-256-GCM. The data key is wrapped to the YubiKey PIV +slot public key with RSA-OAEP-SHA256; unlock requires the YubiKey, PIN, and +touch when configured by the slot policy. + +The in-RAM data-key session mirrors the password vault: the data key is masked +with a random pad and wiped when locked. + +## Browser Profile Vault + +Persistent WebEngine profile data is opt-in with `JERBOA_BROWSER_VAULT=1`. The +browser creates its own encrypted FUSE vault and points Qt cache, storage, and +download directories inside that mount before opening the WebEngine profile. + +Production unlock order: + +- Default: YubiKey PIV-derived passphrase. +- Recovery/testing: `JERBOA_BROWSER_VAULT_PASSPHRASE_FILE`, expected to be a + local 0600 file. +- Testing: `JERBOA_BROWSER_VAULT_UNLOCK=prompt`, which reads from the terminal + with echo disabled. + +The browser does not accept profile-vault passphrases through process +environment variables and does not embed a generated build-local secret. A +release checkout must not contain `scheme/browser/build-vault-secret.ss`. + +## Diagnostics + +Diagnostics may record URLs, page titles, dependency versions, and linkage, but +must not record passwords, PIV PINs, data keys, passphrase file contents, +cookies, local storage, or bookmark plaintext. new file mode 100644 --- /dev/null +++ b/docs/ffi-boundary.md @@ -0,0 +1,40 @@ +# FFI Boundary + +## Qt WebEngine Backend + +The Scheme `(browser)` library binds the C ABI implemented under +`qt-webengine/src`. The ABI uses integer handles for contexts, views, and +windows; the native registry validates handle kind before each operation. + +FFI expectations: + +- Load `libjerboa_browser` through `(jerboa ffi)` `load-shared-object*`. +- Use `define-c-lambda` bindings instead of direct Chez `foreign-procedure` + imports in application libraries. +- Convert strings through explicit UTF-8/length-aware wrappers in the C++ shim. +- Return tagged `(ok ...)` or `(err ...)` values at the Scheme boundary. +- Treat WebEngine page content as hostile even when the Scheme caller is + trusted. + +## Native Dependency Boundaries + +- `libjerboa_browser` wraps Qt WebEngine and the adblock-rust FFI. +- `libyubikey_native` handles direct USB CCID/FIDO2 support when present. +- `libjerboa_fuse_mount.so` is the browser-owned encrypted vault mount helper. +- `libjerboa_native` provides Rust crypto used by secure-store tests and builds. + +All native dependencies must be represented in release evidence with source or +Cargo lockfile hashes, RustSec status where applicable, and dynamic linkage. + +## Memory and Ownership + +Scheme owns high-level controller state. Native code owns WebEngine objects and +frees them through explicit close calls. Returned native strings are copied at +the Scheme boundary or borrowed only long enough to construct a Scheme value. + +Release review should re-check: + +- Stale-handle rejection after close. +- UTF-8/NUL/length handling for URL, JavaScript, title, status, and path inputs. +- Adblock FFI pointer cleanup for redirect buffers. +- Context destruction order: views before profile/interceptor cleanup. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,28 @@ +# Release Evidence + +Run: + +```sh +make release-evidence +``` + +The target runs `make verify`, builds `./jerboa-browser`, and writes evidence to +`dist/release-evidence/`. + +Expected files include: +