Harden IPC, REPL, and dependency provenance
ober
be0ee5d1c30d9eb844697d1325d404948df6ff85
--- a/.build.yml +++ b/.build.yml @@ -17,7 +17,7 @@ packages: - unzip - zip sources: - - https://git.sr.ht/~lisp/jerboa + - https://git.sr.ht/~lisp/jerboa#37426d9bb88e0cef607141605ae4bc928d5ccb24 - https://git.sr.ht/~lisp/jerboa-emacs artifacts: - jerboa-emacs/jerboa-emacs-0.1.0.jpkg --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: build-test-audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Security gate run: make security --- a/.github/workflows/security-baseline.yml +++ b/.github/workflows/security-baseline.yml @@ -13,7 +13,7 @@ jobs: baseline: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Required release files run: | --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ lib/**/*.wpo libjsh-ffi.so /.jerboa/ /dist/ +.jemacs-server +.jerboa-repl-port # Build outputs (binaries, objects, boot files, generated headers) jemacs --- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -3,7 +3,7 @@ "repo": "jerboa-emacs", "extends": ["jerboa:cli", "jerboa:gui", "jerboa:ffi"], "paths": { - "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "src/**/*.{ss,sls,c,h,cpp,hpp}", "scripts/**", "tools/**", "Makefile"], + "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "src/**/*.{ss,sls,c,h,cpp,hpp}", "support/**/*.{c,h,sh}", "scripts/**", "tools/**", "Makefile", "Dockerfile*", "dependencies.lock"], "tests": ["test/**", "tests/**", "**/*-test.ss"], "generated": ["build/**", "dist/**", "target/**", "*.so", "*.dylib", "*.wpo"], "vendor": ["vendor/**", "third_party/**"], @@ -14,9 +14,10 @@ "imports": { "directChezscheme": "allow-in-ffi-boundaries" }, "ffi": { "allowed": true, "requireOwnershipComments": true, "requireDynamicWindCleanup": true }, "process": { "shellInterpolation": "deny", "promptForExternalCommands": true }, - "network": { "requireTimeouts": true }, + "network": { "requireTimeouts": true, "requireAuthentication": true, "requireInputBounds": true }, "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }, "gui": { "requireCallbackLifetimeGuards": true }, + "dependencies": { "requireImmutableGitRevisions": true, "requireArchiveDigests": true, "requireContainerDigests": true }, "releaseEvidence": { "requireEditorFlowProofForProduction": true, "sensitiveArtifactPolicy": "no-buffers-prompts-lsp-payloads-clipboard-or-host-data" --- a/.jerbuild +++ b/.jerbuild @@ -9,9 +9,17 @@ (entry "main.ss") (output "jemacs") +(cc "sh support/deterministic-cc.sh") (requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm") +(static-native #t) + +(os-libs + (Darwin "-lm -lpthread -lncurses -liconv -lc++ -Wl,-no_uuid -Wl,-no_adhoc_codesign") + (Linux "-lm -ldl -lpthread -lutil -lncurses -lstdc++") + (FreeBSD "-lm -lpthread -lutil -ltermcap -L/usr/local/lib -liconv -lstdc++")) + (libdirs "lib" "vendor/jerboa-shell/_jerbuild-stage" "vendor/jerboa-shell/src" @@ -33,9 +41,8 @@ "vendor/jerboa-shell/ffi-shim.c" "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c" "support/jsh_coreutils_stubs.c" - "support/jsh_embed_crypto_stubs.c" - "support/jerboa_native_stubs.c" - "support/jemacs_ssh_agent_stubs.c" - "support/jemacs_tls_stubs.c") + ("support/jsh_embed_crypto_stubs.c" + cflags: "-DJEMACS_STATIC_NATIVE=1") + "support/jemacs_ssh_agent_stubs.c") (pre-build "support/stage-for-jerbuild.sh") --- a/.jerbuild.freebsd-amd64 +++ b/.jerbuild.freebsd-amd64 @@ -14,9 +14,11 @@ (requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm") +(static-native #t) + ;; FreeBSD has dlopen in libc. -lutil covers forkpty/openpty, termcap backs ;; Chez's expeditor, and GNU libiconv is commonly under /usr/local/lib. -(os-libs "-lm -lpthread -lutil -ltermcap -L/usr/local/lib -liconv") +(os-libs "-lm -lpthread -lutil -ltermcap -L/usr/local/lib -liconv -lstdc++") (libdirs "lib" "vendor/jerboa-shell/_jerbuild-stage" @@ -39,9 +41,8 @@ "vendor/jerboa-shell/ffi-shim.c" "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c" "support/jsh_coreutils_stubs.c" - "support/jsh_embed_crypto_stubs.c" - "support/jerboa_native_stubs.c" - "support/jemacs_ssh_agent_stubs.c" - "support/jemacs_tls_stubs.c") + ("support/jsh_embed_crypto_stubs.c" + cflags: "-DJEMACS_STATIC_NATIVE=1") + "support/jemacs_ssh_agent_stubs.c") (pre-build "support/stage-for-jerbuild.sh") --- a/.jerbuild.linux-amd64 +++ b/.jerbuild.linux-amd64 @@ -14,7 +14,9 @@ (requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm") -(os-libs "-lm -ldl -lpthread -lutil -lncurses") +(static-native #t) + +(os-libs "-lm -ldl -lpthread -lutil -lncurses -lstdc++") (libdirs "lib" "vendor/jerboa-shell/_jerbuild-stage" @@ -37,9 +39,8 @@ "vendor/jerboa-shell/ffi-shim.c" "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c" "support/jsh_coreutils_stubs.c" - "support/jsh_embed_crypto_stubs.c" - "support/jerboa_native_stubs.c" - "support/jemacs_ssh_agent_stubs.c" - "support/jemacs_tls_stubs.c") + ("support/jsh_embed_crypto_stubs.c" + cflags: "-DJEMACS_STATIC_NATIVE=1") + "support/jemacs_ssh_agent_stubs.c") (pre-build "support/stage-for-jerbuild.sh") --- a/.jerbuild.linux-arm64 +++ b/.jerbuild.linux-arm64 @@ -14,7 +14,9 @@ (requires "jerbuild" "cc" "pkg-config" "libpcre2" "libvterm") -(os-libs "-lm -ldl -lpthread -lutil -lncurses") +(static-native #t) + +(os-libs "-lm -ldl -lpthread -lutil -lncurses -lstdc++") (libdirs "lib" "vendor/jerboa-shell/_jerbuild-stage" @@ -37,9 +39,8 @@ "vendor/jerboa-shell/ffi-shim.c" "vendor/jerboa-shell/vendor/jerboa-coreutils/support/libcoreutils.c" "support/jsh_coreutils_stubs.c" - "support/jsh_embed_crypto_stubs.c" - "support/jerboa_native_stubs.c" - "support/jemacs_ssh_agent_stubs.c" - "support/jemacs_tls_stubs.c") + ("support/jsh_embed_crypto_stubs.c" + cflags: "-DJEMACS_STATIC_NATIVE=1") + "support/jemacs_ssh_agent_stubs.c") (pre-build "support/stage-for-jerbuild.sh") --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ # itself (~5-10 min instead of ~30 min). # ARG ARCH=x86_64 -FROM alpine:3.21 +FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d # ── Phase 1: Alpine build deps ────────────────────────────────────────── RUN apk add --no-cache \ @@ -29,9 +29,11 @@ RUN apk add --no-cache \ gcc g++ binutils make git curl wget # Build static libXau (no Alpine -static package available) +ARG LIBXAU_SHA256=74d0e4dfa3d39ad8939e99bda37f5967aba528211076828464d2777d477fc0fb RUN apk add --no-cache libxau-dev && \ cd /tmp && \ wget -q https://xorg.freedesktop.org/releases/individual/lib/libXau-1.0.12.tar.xz && \ + echo "${LIBXAU_SHA256} libXau-1.0.12.tar.xz" | sha256sum -c - && \ tar xf libXau-1.0.12.tar.xz && \ cd libXau-1.0.12 && \ ./configure --prefix=/usr --enable-static --disable-shared && \ @@ -39,8 +41,10 @@ RUN apk add --no-cache libxau-dev && \ cd / && rm -rf /tmp/libXau-1.0.12* # Build static libxcb-util (no Alpine -static package; needed by xcb-image) +ARG XCB_UTIL_SHA256=5abe3bbbd8e54f0fa3ec945291b7e8fa8cfd3cccc43718f8758430f94126e512 RUN cd /tmp && \ wget -q https://xcb.freedesktop.org/dist/xcb-util-0.4.1.tar.xz && \ + echo "${XCB_UTIL_SHA256} xcb-util-0.4.1.tar.xz" | sha256sum -c - && \ tar xf xcb-util-0.4.1.tar.xz && \ cd xcb-util-0.4.1 && \ ./configure --prefix=/usr --enable-static --disable-shared && \ @@ -49,20 +53,25 @@ RUN cd /tmp && \ # Build static OpenSSL (needed by jerboa-ssl for AWS API calls) # Alpine's openssl-dev only ships shared libs; we need .a files for the static binary. +ARG OPENSSL_VERSION=3.5.7 +ARG OPENSSL_SHA256=a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8 RUN apk add --no-cache openssl-dev perl && \ - OPENSSL_VER=$(apk info openssl 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "3.3.2") && \ - wget -q https://www.openssl.org/source/openssl-${OPENSSL_VER}.tar.gz || \ - wget -q https://www.openssl.org/source/openssl-3.3.2.tar.gz && \ - tar xf openssl-*.tar.gz && \ - cd openssl-*/ && \ - ./Configure linux-x86_64 no-shared no-tests no-apps -O2 --prefix=/usr && \ + wget -q "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" && \ + echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c - && \ + tar xf "openssl-${OPENSSL_VERSION}.tar.gz" && \ + cd "openssl-${OPENSSL_VERSION}" && \ + ./Configure no-shared no-tests -O2 --prefix=/usr && \ make -j$(nproc) && \ + ./apps/openssl version | grep -F "OpenSSL ${OPENSSL_VERSION}" && \ + ./apps/openssl version -a > /usr/lib/jemacs-openssl-build.txt && \ cp libssl.a libcrypto.a /usr/lib/ && \ - cd / && rm -rf openssl-*/ openssl-*.tar.gz + cd / && rm -rf "openssl-${OPENSSL_VERSION}" "openssl-${OPENSSL_VERSION}.tar.gz" # ── Phase 2: Build Qt6 qtbase static ──────────────────────────────────── ARG QT6_VERSION=6.8.3 +ARG QTBASE_SHA256=56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submodules/qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + echo "${QTBASE_SHA256} qtbase-everywhere-src-${QT6_VERSION}.tar.xz" | sha256sum -c - && \ tar xf qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ rm qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ cmake -S qtbase-everywhere-src-${QT6_VERSION} -B qt6-build -G Ninja \ @@ -88,7 +97,9 @@ RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submo # ── Phase 3: Build QScintilla static ──────────────────────────────────── ARG QSCI_VERSION=2.14.1 +ARG QSCINTILLA_SHA256=dfe13c6acc9d85dfcba76ccc8061e71a223957a6c02f3c343b30a9d43a4cdd4d RUN wget -q https://www.riverbankcomputing.com/static/Downloads/QScintilla/${QSCI_VERSION}/QScintilla_src-${QSCI_VERSION}.tar.gz && \ + echo "${QSCINTILLA_SHA256} QScintilla_src-${QSCI_VERSION}.tar.gz" | sha256sum -c - && \ tar xf QScintilla_src-${QSCI_VERSION}.tar.gz && \ rm QScintilla_src-${QSCI_VERSION}.tar.gz && \ cd QScintilla_src-${QSCI_VERSION}/src && \ @@ -186,15 +197,13 @@ RUN mkdir -p /opt/qt6-static/lib/pkgconfig && \ # This is needed because jemacs embeds the program as a .so and loads it # at runtime via Sscheme_script (which calls dlopen internally). # musl's static libdl.a provides dlopen in the final static binary. -ARG CHEZ_TAG=main -ARG CHEZ_COMMIT= -RUN git clone --depth 100 --branch ${CHEZ_TAG} \ - https://github.com/cisco/ChezScheme /tmp/ChezScheme && \ +ARG CHEZ_COMMIT=fdf6b3f5d069bf53082bb827f46714f2de8f11f5 +RUN git init -q /tmp/ChezScheme && \ + git -C /tmp/ChezScheme remote add origin https://github.com/cisco/ChezScheme && \ + git -C /tmp/ChezScheme fetch -q --depth 1 origin ${CHEZ_COMMIT} && \ + test "$(git -C /tmp/ChezScheme rev-parse FETCH_HEAD)" = "${CHEZ_COMMIT}" && \ + git -C /tmp/ChezScheme checkout -q --detach ${CHEZ_COMMIT} && \ cd /tmp/ChezScheme && \ - if [ -n "${CHEZ_COMMIT}" ]; then \ - echo "Pinning Chez to commit ${CHEZ_COMMIT}"; \ - git checkout ${CHEZ_COMMIT}; \ - fi && \ ./configure --threads --installprefix=/opt/chez && \ make -j$(nproc) && \ make install && \ @@ -202,8 +211,12 @@ RUN git clone --depth 100 --branch ${CHEZ_TAG} \ # ── Phase 5.5: Build tree-sitter + grammars (static) ────────────────── ARG TS_VERSION=0.24.7 -RUN git clone --depth 1 --branch v${TS_VERSION} \ - https://github.com/tree-sitter/tree-sitter /tmp/tree-sitter && \ +ARG TS_COMMIT=5e8760bf462ce7b19b3d2396d5b7860f3906a297 +RUN git init -q /tmp/tree-sitter && \ + git -C /tmp/tree-sitter remote add origin https://github.com/tree-sitter/tree-sitter && \ + git -C /tmp/tree-sitter fetch -q --depth 1 origin ${TS_COMMIT} && \ + test "$(git -C /tmp/tree-sitter rev-parse FETCH_HEAD)" = "${TS_COMMIT}" && \ + git -C /tmp/tree-sitter checkout -q --detach ${TS_COMMIT} && \ cd /tmp/tree-sitter && \ cc -c -O2 -Ilib/include lib/src/lib.c -o lib/src/lib.o && \ mkdir -p /opt/tree-sitter-lib && \ @@ -215,9 +228,12 @@ RUN git clone --depth 1 --branch v${TS_VERSION} \ # Build tree-sitter grammar static archives RUN mkdir -p /opt/tree-sitter-grammars && \ build_grammar() { \ - name=$1; repo=$2; tag=$3; subdir=${4:-.}; \ - git clone --depth 1 --branch "$tag" \ - "https://github.com/$repo" /tmp/ts-$name && \ + name=$1; repo=$2; commit=$3; subdir=${4:-.}; \ + git init -q /tmp/ts-$name && \ + git -C /tmp/ts-$name remote add origin "https://github.com/$repo" && \ + git -C /tmp/ts-$name fetch -q --depth 1 origin "$commit" && \ + test "$(git -C /tmp/ts-$name rev-parse FETCH_HEAD)" = "$commit" && \ + git -C /tmp/ts-$name checkout -q --detach "$commit" && \ cd /tmp/ts-$name/$subdir && \ SRC_DIR=src && \ gcc -c -O2 -I/opt/tree-sitter-include -I$SRC_DIR \ @@ -231,20 +247,20 @@ RUN mkdir -p /opt/tree-sitter-grammars && \ fi && \ cd / && rm -rf /tmp/ts-$name; \ } && \ - build_grammar c tree-sitter/tree-sitter-c v0.23.5 && \ - build_grammar cpp tree-sitter/tree-sitter-cpp v0.23.4 && \ - build_grammar python tree-sitter/tree-sitter-python v0.23.6 && \ - build_grammar javascript tree-sitter/tree-sitter-javascript v0.23.1 && \ - build_grammar rust tree-sitter/tree-sitter-rust v0.23.3 && \ - build_grammar go tree-sitter/tree-sitter-go v0.23.4 && \ - build_grammar bash tree-sitter/tree-sitter-bash v0.23.3 && \ - build_grammar json tree-sitter/tree-sitter-json v0.24.8 && \ - build_grammar ruby tree-sitter/tree-sitter-ruby v0.23.1 && \ - build_grammar java tree-sitter/tree-sitter-java v0.23.5 && \ - build_grammar css tree-sitter/tree-sitter-css v0.23.2 && \ - build_grammar html tree-sitter/tree-sitter-html v0.23.2 && \ - build_grammar lua tree-sitter-grammars/tree-sitter-lua v0.3.0 && \ - build_grammar scheme 6cdh/tree-sitter-scheme main && \ + build_grammar c tree-sitter/tree-sitter-c 2a265d69a4caf57108a73ad2ed1e6922dd2f998c && \ + build_grammar cpp tree-sitter/tree-sitter-cpp f41e1a044c8a84ea9fa8577fdd2eab92ec96de02 && \ + build_grammar python tree-sitter/tree-sitter-python bffb65a8cfe4e46290331dfef0dbf0ef3679de11 && \ + build_grammar javascript tree-sitter/tree-sitter-javascript 3a837b6f3658ca3618f2022f8707e29739c91364 && \ + build_grammar rust tree-sitter/tree-sitter-rust 3d087c3df25286140393ddecc339208fae107149 && \ + build_grammar go tree-sitter/tree-sitter-go 3c3775faa968158a8b4ac190a7fda867fd5fb748 && \ + build_grammar bash tree-sitter/tree-sitter-bash 487734f87fd87118028a65a4599352fa99c9cde8 && \ + build_grammar json tree-sitter/tree-sitter-json ee35a6ebefcef0c5c416c0d1ccec7370cfca5a24 && \ + build_grammar ruby tree-sitter/tree-sitter-ruby 71bd32fb7607035768799732addba884a37a6210 && \ + build_grammar java tree-sitter/tree-sitter-java 94703d5a6bed02b98e438d7cad1136c01a60ba2c && \ + build_grammar css tree-sitter/tree-sitter-css c0d581e32d183a536731ed6c3a72758b27e20411 && \ + build_grammar html tree-sitter/tree-sitter-html 5a5ca8551a179998360b4a4ca2c0f366a35acc03 && \ + build_grammar lua tree-sitter-grammars/tree-sitter-lua 534c461d2b75b0887ec968ef9635f4460b0878b7 && \ + build_grammar scheme 6cdh/tree-sitter-scheme c6cb7c7d7a04b3f5d999c28e2e9c0c31b2d50ece && \ echo "Grammars built:" && ls /opt/tree-sitter-grammars/ # ── Phase 6: Jerboa dependencies ─────────────────────────────────────── --- a/Dockerfile.qt +++ b/Dockerfile.qt @@ -11,16 +11,21 @@ # docker cp $id:/out/jemacs-qt ./jemacs-qt && docker rm $id # chmod +x jemacs-qt -ARG JERBOA_IMAGE=jerboa21/jerboa +ARG JERBOA_IMAGE=jerboa21/jerboa@sha256:9c4034f1296601d5a9f7b219a0decdd5d62862a2be4347976b3d96b13ed7701e FROM ${JERBOA_IMAGE} AS jerboa-runtime # ── Stage 1: Build Qt6 + QScintilla + tree-sitter + libvterm on Alpine ─────── -FROM alpine:3.21 AS qt-alpine +FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d AS qt-alpine ARG QT6_VERSION=6.8.3 ARG QSCI_VERSION=2.14.1 ARG TS_VERSION=0.24.7 +ARG TS_COMMIT=5e8760bf462ce7b19b3d2396d5b7860f3906a297 +ARG LIBXAU_SHA256=74d0e4dfa3d39ad8939e99bda37f5967aba528211076828464d2777d477fc0fb +ARG XCB_UTIL_SHA256=5abe3bbbd8e54f0fa3ec945291b7e8fa8cfd3cccc43718f8758430f94126e512 +ARG QTBASE_SHA256=56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 +ARG QSCINTILLA_SHA256=dfe13c6acc9d85dfcba76ccc8061e71a223957a6c02f3c343b30a9d43a4cdd4d # ── Phase 1: Alpine build deps ────────────────────────────────────────── RUN apk add --no-cache \ @@ -47,6 +52,7 @@ RUN apk add --no-cache \ RUN apk add --no-cache libxau-dev && \ cd /tmp && \ wget -q https://xorg.freedesktop.org/releases/individual/lib/libXau-1.0.12.tar.xz && \ + echo "${LIBXAU_SHA256} libXau-1.0.12.tar.xz" | sha256sum -c - && \ tar xf libXau-1.0.12.tar.xz && \ cd libXau-1.0.12 && \ ./configure --prefix=/usr --enable-static --disable-shared && \ @@ -56,6 +62,7 @@ RUN apk add --no-cache libxau-dev && \ # Build static libxcb-util (no Alpine -static package; needed by xcb-image) RUN cd /tmp && \ wget -q https://xcb.freedesktop.org/dist/xcb-util-0.4.1.tar.xz && \ + echo "${XCB_UTIL_SHA256} xcb-util-0.4.1.tar.xz" | sha256sum -c - && \ tar xf xcb-util-0.4.1.tar.xz && \ cd xcb-util-0.4.1 && \ ./configure --prefix=/usr --enable-static --disable-shared && \ @@ -64,6 +71,7 @@ RUN cd /tmp && \ # ── Phase 2: Build Qt6 qtbase static ──────────────────────────────────── RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submodules/qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ + echo "${QTBASE_SHA256} qtbase-everywhere-src-${QT6_VERSION}.tar.xz" | sha256sum -c - && \ tar xf qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ rm qtbase-everywhere-src-${QT6_VERSION}.tar.xz && \ cmake -S qtbase-everywhere-src-${QT6_VERSION} -B qt6-build -G Ninja \ @@ -93,6 +101,7 @@ RUN wget -q https://download.qt.io/official_releases/qt/6.8/${QT6_VERSION}/submo # ── Phase 3: Build QScintilla static ──────────────────────────────────── RUN wget -q https://www.riverbankcomputing.com/static/Downloads/QScintilla/${QSCI_VERSION}/QScintilla_src-${QSCI_VERSION}.tar.gz && \ + echo "${QSCINTILLA_SHA256} QScintilla_src-${QSCI_VERSION}.tar.gz" | sha256sum -c - && \ tar xf QScintilla_src-${QSCI_VERSION}.tar.gz && \ rm QScintilla_src-${QSCI_VERSION}.tar.gz && \ cd QScintilla_src-${QSCI_VERSION}/src && \ @@ -181,8 +190,11 @@ RUN mkdir -p /opt/qt6-static/lib/pkgconfig && \ echo "Generated .pc files:" && ls /opt/qt6-static/lib/pkgconfig/ # ── Phase 5: Build tree-sitter + grammars (static) ────────────────────── -RUN git clone --depth 1 --branch v${TS_VERSION} \ - https://github.com/tree-sitter/tree-sitter /tmp/tree-sitter && \ +RUN git init -q /tmp/tree-sitter && \ + git -C /tmp/tree-sitter remote add origin https://github.com/tree-sitter/tree-sitter && \ + git -C /tmp/tree-sitter fetch -q --depth 1 origin ${TS_COMMIT} && \ + test "$(git -C /tmp/tree-sitter rev-parse FETCH_HEAD)" = "${TS_COMMIT}" && \ + git -C /tmp/tree-sitter checkout -q --detach ${TS_COMMIT} && \ cd /tmp/tree-sitter && \ cc -c -O2 -Ilib/include lib/src/lib.c -o lib/src/lib.o && \ mkdir -p /opt/tree-sitter-lib && \ @@ -193,9 +205,12 @@ RUN git clone --depth 1 --branch v${TS_VERSION} \ RUN mkdir -p /opt/tree-sitter-grammars && \ build_grammar() { \ - name=$1; repo=$2; tag=$3; subdir=${4:-.}; \ - git clone --depth 1 --branch "$tag" \ - "https://github.com/$repo" /tmp/ts-$name && \ + name=$1; repo=$2; commit=$3; subdir=${4:-.}; \ + git init -q /tmp/ts-$name && \ + git -C /tmp/ts-$name remote add origin "https://github.com/$repo" && \ + git -C /tmp/ts-$name fetch -q --depth 1 origin "$commit" && \ + test "$(git -C /tmp/ts-$name rev-parse FETCH_HEAD)" = "$commit" && \ + git -C /tmp/ts-$name checkout -q --detach "$commit" && \ cd /tmp/ts-$name/$subdir && \ SRC_DIR=src && \ gcc -c -O2 -I/opt/tree-sitter-include -I$SRC_DIR \ @@ -209,20 +224,20 @@ RUN mkdir -p /opt/tree-sitter-grammars && \ fi && \ cd / && rm -rf /tmp/ts-$name; \ } && \ - build_grammar c tree-sitter/tree-sitter-c v0.23.5 && \ - build_grammar cpp tree-sitter/tree-sitter-cpp v0.23.4 && \ - build_grammar python tree-sitter/tree-sitter-python v0.23.6 && \ - build_grammar javascript tree-sitter/tree-sitter-javascript v0.23.1 && \ - build_grammar rust tree-sitter/tree-sitter-rust v0.23.3 && \ - build_grammar go tree-sitter/tree-sitter-go v0.23.4 && \ - build_grammar bash tree-sitter/tree-sitter-bash v0.23.3 && \ - build_grammar json tree-sitter/tree-sitter-json v0.24.8 && \ - build_grammar ruby tree-sitter/tree-sitter-ruby v0.23.1 && \ - build_grammar java tree-sitter/tree-sitter-java v0.23.5 && \ - build_grammar css tree-sitter/tree-sitter-css v0.23.2 && \ - build_grammar html tree-sitter/tree-sitter-html v0.23.2 && \ - build_grammar lua tree-sitter-grammars/tree-sitter-lua v0.3.0 && \ - build_grammar scheme 6cdh/tree-sitter-scheme main && \ + build_grammar c tree-sitter/tree-sitter-c 2a265d69a4caf57108a73ad2ed1e6922dd2f998c && \ + build_grammar cpp tree-sitter/tree-sitter-cpp f41e1a044c8a84ea9fa8577fdd2eab92ec96de02 && \ + build_grammar python tree-sitter/tree-sitter-python bffb65a8cfe4e46290331dfef0dbf0ef3679de11 && \ + build_grammar javascript tree-sitter/tree-sitter-javascript 3a837b6f3658ca3618f2022f8707e29739c91364 && \ + build_grammar rust tree-sitter/tree-sitter-rust 3d087c3df25286140393ddecc339208fae107149 && \ + build_grammar go tree-sitter/tree-sitter-go 3c3775faa968158a8b4ac190a7fda867fd5fb748 && \ + build_grammar bash tree-sitter/tree-sitter-bash 487734f87fd87118028a65a4599352fa99c9cde8 && \ + build_grammar json tree-sitter/tree-sitter-json ee35a6ebefcef0c5c416c0d1ccec7370cfca5a24 && \ + build_grammar ruby tree-sitter/tree-sitter-ruby 71bd32fb7607035768799732addba884a37a6210 && \ + build_grammar java tree-sitter/tree-sitter-java 94703d5a6bed02b98e438d7cad1136c01a60ba2c && \ + build_grammar css tree-sitter/tree-sitter-css c0d581e32d183a536731ed6c3a72758b27e20411 && \ + build_grammar html tree-sitter/tree-sitter-html 5a5ca8551a179998360b4a4ca2c0f366a35acc03 && \ + build_grammar lua tree-sitter-grammars/tree-sitter-lua 534c461d2b75b0887ec968ef9635f4460b0878b7 && \ + build_grammar scheme 6cdh/tree-sitter-scheme c6cb7c7d7a04b3f5d999c28e2e9c0c31b2d50ece && \ echo "Grammars built:" && ls /opt/tree-sitter-grammars/ # ── Phase 6: Stage Qt transitive static deps for the final link ───────── @@ -298,6 +313,6 @@ RUN ./jemacs-qt --version && \ ls -lh jemacs-qt # ── Final stage: extract binary ─────────────────────────────────────────────── -FROM ubuntu:24.04 +FROM ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 COPY --from=builder /src/jemacs-qt /out/jemacs-qt CMD ["cat", "/out/jemacs-qt"] --- a/Dockerfile.tui +++ b/Dockerfile.tui @@ -8,7 +8,7 @@ # podman cp $id:/out/jemacs ./jemacs && podman rm $id # chmod +x jemacs -FROM jerboa21/jerboa AS builder +FROM jerboa21/jerboa@sha256:9c4034f1296601d5a9f7b219a0decdd5d62862a2be4347976b3d96b13ed7701e AS builder # ── Copy jerboa-emacs source ──────────────────────────────────────────────── COPY . /src @@ -24,6 +24,6 @@ RUN ./jemacs --version && \ ls -lh jemacs # ── Output ─────────────────────────────────────────────────────────────────── -FROM ubuntu:24.04 +FROM ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 COPY --from=builder /src/jemacs /out/jemacs CMD ["cat", "/out/jemacs"] --- a/Makefile +++ b/Makefile @@ -9,8 +9,9 @@ RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence # Standalone jerbuild toolchain. This mirrors the bootstrap path used by # jerboa-code/jerboa-shell: prefer project-local tools, then sibling Jerboa, # then PATH, and otherwise fetch a release into .jerboa/bin. -JERBOA_VERSION ?= v0.2.0 +JERBOA_VERSION ?= $(JERBOA_TOOLCHAIN_VERSION) JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin +DETERMINISTIC_JERBUILD ?= $(CURDIR)/support/deterministic-jerbuild.sh JERBUILD_TOOL ?= $(shell if [ -x ./jerbuild ]; then echo ./jerbuild; \ elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \ elif [ -x ../jerboa/dist/jerbuild ]; then echo ../jerboa/dist/jerbuild; \ @@ -22,7 +23,7 @@ SSH ?= ssh SCP ?= scp LINUX_BUILD_HOST ?= linux.cons.io REMOTE_REPO_URL ?= $(shell git remote get-url origin 2>/dev/null || echo git@git.sr.ht:~lisp/jerboa-emacs) -REMOTE_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo master) +REMOTE_COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null) LINUX_BUILD_ROOT ?= .cache/jemacs-build/jerboa-emacs LINUX_ARM64_BUILD_HOST ?= LINUX_ARM64_BUILD_ROOT ?= $(LINUX_BUILD_ROOT) @@ -38,8 +39,14 @@ JSQLITE_REPO_URL ?= https://git.sr.ht/~lisp/jsqlite JERBOA_LSP_REPO ?= https://git.sr.ht/~lisp/jerboa-lsp JERBOA_AWS_REPO ?= https://git.sr.ht/~lisp/jerboa-aws +DEPENDENCY_LOCK ?= dependencies.lock +include $(DEPENDENCY_LOCK) + JEMACS_FEATURES ?= export JEMACS_FEATURES +JEMACS_DEV_NATIVE ?= 1 +JERBOA_EMACS_SUPPORT ?= $(CURDIR) +export JEMACS_DEV_NATIVE JERBOA_EMACS_SUPPORT empty := space := $(empty) $(empty) @@ -126,6 +133,7 @@ else TS_LIB_DIR := /opt/tree-sitter-lib endif NATIVE_LIB := $(JERBOA)/lib/libjerboa_native.$(SHLIB_EXT) +JERBOA_NATIVE_A ?= $(JERBOA)/jerboa-native-rs/target/release/libjerboa_native.a SSL_SHIM := $(JERBOA_SSL_ROOT)/jerboa_ssl_shim.$(SHLIB_EXT) SCINTILLA_SHIM := ./jerboa_scintilla_shim.$(SHLIB_EXT) TLS_STUB := ./jemacs_tls_stubs.$(SHLIB_EXT) @@ -155,7 +163,7 @@ export CHEZ_DIR .PHONY: all vendor-deps vendor-update check-pcre2-system-deps check-vterm-system-deps deps ensure-vendor-jerboa-tools ensure-jerboa-tools build-jerbuild jemacs-jerbuild jerbuild-check scintilla-vendor-deps scintilla-vendor-clean build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated install audit security sbom reproducibility-report editor-flow-evidence verify release-evidence \ test-org-duration test-org-element test-org-fold test-org-footnote \ test-org-lint test-org-num test-org-property test-org-src test-org-tempo \ - test-vtscreen test-debug-repl test-qt test-qt-e2e build-qt binary-qt \ + test-vtscreen test-debug-repl test-ipc-security test-repl-shim test-qt test-qt-e2e build-qt binary-qt \ test-pty test-emacs test-functional test-term-hang \ podman-deps ensure-podman-deps static-qt static-tui clean-podman check-podman check-root \ check-docker docker-static-qt \ @@ -217,16 +225,16 @@ vendor-deps: @mkdir -p $(VENDOR) @for repo in $(VENDOR_REPOS); do \ case "$$repo" in \ - jerboa) url="$(JERBOA_REPO)" ;; \ - jerboa-shell) url="$(JERBOA_SHELL_REPO)" ;; \ - jerboa-pcre2) url="$(JERBOA_PCRE2_REPO)" ;; \ - jerboa-scintilla) url="$(JERBOA_SCINTILLA_REPO)" ;; \ - jerboa-qt) url="$(JERBOA_QT_REPO)" ;; \ - jerboa-ssl) url="$(JERBOA_SSL_REPO)" ;; \ - jerboa-https) url="$(JERBOA_HTTPS_REPO)" ;; \ - jsqlite) url="$(JSQLITE_REPO_URL)" ;; \ - jerboa-lsp) url="$(JERBOA_LSP_REPO)" ;; \ - jerboa-aws) url="$(JERBOA_AWS_REPO)" ;; \ + jerboa) url="$(JERBOA_REPO)"; rev="$(JERBOA_REV)" ;; \ + jerboa-shell) url="$(JERBOA_SHELL_REPO)"; rev="$(JERBOA_SHELL_REV)" ;; \ + jerboa-pcre2) url="$(JERBOA_PCRE2_REPO)"; rev="$(JERBOA_PCRE2_REV)" ;; \ + jerboa-scintilla) url="$(JERBOA_SCINTILLA_REPO)"; rev="$(JERBOA_SCINTILLA_REV)" ;; \ + jerboa-qt) url="$(JERBOA_QT_REPO)"; rev="$(JERBOA_QT_REV)" ;; \ + jerboa-ssl) url="$(JERBOA_SSL_REPO)"; rev="$(JERBOA_SSL_REV)" ;; \ + jerboa-https) url="$(JERBOA_HTTPS_REPO)"; rev="$(JERBOA_HTTPS_REV)" ;; \ + jsqlite) url="$(JSQLITE_REPO_URL)"; rev="$(JSQLITE_REV)" ;; \ + jerboa-lsp) url="$(JERBOA_LSP_REPO)"; rev="$(JERBOA_LSP_REV)" ;; \ + jerboa-aws) url="$(JERBOA_AWS_REPO)"; rev="$(JERBOA_AWS_REV)" ;; \ esac; \ dest="$(VENDOR)/$$repo"; \ if [ -L "$$dest" ]; then \ @@ -234,47 +242,57 @@ vendor-deps: rm "$$dest"; \ fi; \ if [ -d "$$dest/.git" ]; then \ - echo " (exists) $$repo"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ elif [ -d "$$dest" ] && [ -z "$$(ls -A "$$dest")" ]; then \ echo " replacing empty gitlink directory $$dest"; \ rmdir "$$dest"; \ echo " cloning $$repo from $$url"; \ - git clone --depth 1 "$$url" "$$dest"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ elif [ -d "$$dest" ]; then \ echo " replacing non-git vendor directory $$dest"; \ rm -rf "$$dest"; \ echo " cloning $$repo from $$url"; \ - git clone --depth 1 "$$url" "$$dest"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ elif [ -e "$$dest" ]; then \ echo "ERROR: $$dest exists but is not a directory; remove it and rerun make vendor-deps" >&2; \ exit 1; \ else \ echo " cloning $$repo from $$url"; \ - git clone --depth 1 "$$url" "$$dest"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ fi; \ done @echo "=== Vendoring jsh dependencies into $(JSH_VENDOR) ===" @mkdir -p $(JSH_VENDOR) @for repo in $(JSH_VENDOR_REPOS); do \ + case "$$repo" in \ + jerboa-coreutils) rev="$(JSH_COREUTILS_REV)" ;; \ + jerboa-awk) rev="$(JSH_AWK_REV)" ;; \ + jerboa-sed) rev="$(JSH_SED_REV)" ;; \ + jerboa-aws) rev="$(JSH_AWS_REV)" ;; \ + jerboa-yubikey) rev="$(JSH_YUBIKEY_REV)" ;; \ + jerboa-ssh) rev="$(JSH_SSH_REV)" ;; \ + jerboa-fuse) rev="$(JSH_FUSE_REV)" ;; \ + esac; \ + url="https://git.sr.ht/~lisp/$$repo"; \ dest="$(JSH_VENDOR)/$$repo"; \ if [ -d "$$dest/.git" ]; then \ - echo " (exists) $$repo"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ elif [ -d "$$dest" ] && [ -z "$$(ls -A "$$dest")" ]; then \ echo " replacing empty directory $$dest"; \ rmdir "$$dest"; \ echo " cloning $$repo"; \ - git clone --depth 1 "https://git.sr.ht/~lisp/$$repo" "$$dest"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ elif [ -d "$$dest" ]; then \ echo " replacing non-git vendor directory $$dest"; \ rm -rf "$$dest"; \ echo " cloning $$repo"; \ - git clone --depth 1 "https://git.sr.ht/~lisp/$$repo" "$$dest"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ elif [ -e "$$dest" ]; then \ echo "ERROR: $$dest exists but is not a directory; remove it and rerun make vendor-deps" >&2; \ exit 1; \ else \ echo " cloning $$repo"; \ - git clone --depth 1 "https://git.sr.ht/~lisp/$$repo" "$$dest"; \ + sh support/fetch-locked-dependency.sh "$$url" "$$rev" "$$dest"; \ fi; \ done @sh scripts/patch-jerboa-std-compat.sh "$(abspath $(JERBOA)/lib)" @@ -283,32 +301,12 @@ vendor-deps: @sh scripts/patch-jerboa-qt-compat.sh "$(CURDIR)" @echo "=== Vendor complete ===" -# Fast-forward existing vendored git checkouts to current upstream HEAD. -# `make vendor-deps` only CLONES missing deps; it never updates an existing -# checkout, so a machine with old clones keeps building against stale sources -# (the usual cause of "No rule to make target 'jerboa'/'stage-jsqlite'" and -# pcre2 "multiple definitions for match"). This resets each tracked vendor/<dep> -# to origin HEAD. Gitignored build artifacts (.chez/, build/) are preserved; -# local patch-script edits re-apply on the next `make build`. +# Compatibility alias retained for operators. Dependencies are never advanced +# to mutable upstream HEAD: this target verifies/fetches dependencies.lock. vendor-update: - @echo "=== Updating vendored git checkouts to upstream HEAD ===" - @for d in $(VENDOR)/*/ ; do \ - d=$${d%/}; \ - [ -d "$$d/.git" ] || continue; \ - cur=$$(git -C "$$d" rev-parse --short HEAD 2>/dev/null); \ - if git -C "$$d" fetch --depth 1 origin HEAD >/dev/null 2>&1; then \ - new=$$(git -C "$$d" rev-parse --short FETCH_HEAD 2>/dev/null); \ - if [ "$$cur" != "$$new" ]; then \ - git -C "$$d" reset --hard FETCH_HEAD >/dev/null 2>&1 \ - && echo " updated $$(basename $$d): $$cur -> $$new"; \ - else \ - echo " current $$(basename $$d): $$cur"; \ - fi; \ - else \ - echo " skip $$(basename $$d) (no reachable upstream)"; \ - fi; \ - done - @echo "=== vendor-update complete — run 'make build' to re-apply compat patches ===" + @echo "=== Verifying locked vendored dependency revisions ===" + @$(MAKE) --no-print-directory vendor-deps + @echo "=== vendor-update complete — no mutable upstream refs consumed ===" PCRE2_CHECK_CFLAGS := $(shell pkg-config --cflags libpcre2-8 2>/dev/null) PCRE2_CHECK_LIBS := $(shell pkg-config --libs libpcre2-8 2>/dev/null || echo -lpcre2-8) @@ -370,14 +368,14 @@ ensure-jerboa-tools: build-jerbuild: ensure-jerboa-tools vendor-deps JERBUILD="$(JERBUILD_TOOL)" support/stage-for-jerbuild.sh -jerbuild-check: ensure-jerboa-tools +jerbuild-check: ensure-vendor-jerboa-tools @for cfg in .jerbuild .jerbuild.linux-amd64 .jerbuild.linux-arm64 .jerbuild.freebsd-amd64; do \ echo "=== $$cfg ==="; \ - "$(JERBUILD_TOOL)" check --config "$$cfg"; \ + "$(VENDORED_JERBUILD)" check --config "$$cfg"; \ done -jemacs-jerbuild: ensure-jerboa-tools vendor-deps repl_shim.$(SHLIB_EXT) pty_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_scintilla_shim.$(SHLIB_EXT) jerboa_pcre2_shim.$(SHLIB_EXT) - JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild +jemacs-jerbuild: ensure-vendor-jerboa-tools vendor-deps $(NATIVE_LIB) repl_shim.$(SHLIB_EXT) pty_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_scintilla_shim.$(SHLIB_EXT) jerboa_pcre2_shim.$(SHLIB_EXT) + JERBOA_NATIVE_A="$(abspath $(JERBOA_NATIVE_A))" JERBUILD="$(VENDORED_JERBUILD)" JEMACS_PINNED_JERBUILD="$(VENDORED_JERBUILD)" "$(DETERMINISTIC_JERBUILD)" build --config .jerbuild # Generate lib/jerboa-emacs/*.sls from src/jerboa-emacs/*.ss (incremental) build: deps @@ -388,8 +386,16 @@ rebuild: deps $(JERBUILD) src/ lib/ --force check-generated: - $(MAKE) build - git diff --exit-code -- lib + @before=$$(mktemp); after=$$(mktemp); \ + trap 'rm -f "$$before" "$$after"' EXIT HUP INT TERM; \ + git diff --binary -- lib > "$$before"; \ + $(MAKE) build; \ + git diff --binary -- lib > "$$after"; \ + if ! cmp -s "$$before" "$$after"; then \ + echo "Generated lib/ files changed; run 'make build' and commit them." >&2; \ + diff -u "$$before" "$$after" || true; \ + exit 1; \ + fi audit: sh scripts/native-dependency-audit.sh @@ -398,10 +404,10 @@ security: REPO_ROOT="$(CURDIR)" sh scripts/security-check.sh sbom: build - JERBUILD_TOOL="$(JERBUILD_TOOL)" SBOM_DIR="$(SBOM_DIR)" scripts/sbom.sh + JERBUILD_TOOL="$(VENDORED_JERBUILD)" SBOM_DIR="$(SBOM_DIR)" scripts/sbom.sh -reproducibility-report: - JERBUILD_TOOL="$(JERBUILD_TOOL)" REPRO_DIR="$(REPRO_DIR)" MAKE="$(MAKE)" scripts/reproducibility-report.sh +reproducibility-report: ensure-vendor-jerboa-tools + JERBUILD_TOOL="$(VENDORED_JERBUILD)" REPRO_DIR="$(REPRO_DIR)" MAKE="$(MAKE)" scripts/reproducibility-report.sh editor-flow-evidence: EDITOR_FLOW_EVIDENCE_DIR="$(EDITOR_FLOW_EVIDENCE_DIR)" scripts/editor-flow-evidence.sh @@ -700,7 +706,7 @@ test-persist: test-extra: test-org-duration test-org-element test-org-fold test-org-footnote \ test-org-lint test-org-num test-org-property test-org-src test-org-tempo \ - test-vtscreen test-debug-repl test-qt + test-vtscreen test-debug-repl test-ipc-security test-repl-shim test-qt test-org-duration: $(SCHEME) $(LIBDIRS) --script tests/test-org-duration.ss @@ -735,6 +741,18 @@ test-vtscreen: jerboa_scintilla_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh test-debug-repl: $(SCHEME) $(LIBDIRS) --program tests/test-debug-repl.ss +test-ipc-security: repl_shim.$(SHLIB_EXT) + @tmp=$$(mktemp -d "$${TMPDIR:-/tmp}/jemacs-ipc-test.XXXXXX"); \ + trap 'rm -rf "$$tmp"' EXIT HUP INT TERM; \ + JEMACS_TEST_TMPDIR="$$tmp" $(SCHEME) $(LIBDIRS) --program tests/test-ipc-security.ss + +test-repl-shim: + @mkdir -p .jerboa/test + $(CC) -O2 -Wall -Wextra -o .jerboa/test/test-repl-shim \ + tests/test-repl-shim.c support/repl_shim.c + @.jerboa/test/test-repl-shim + @rm -f .jerboa/test/test-repl-shim + test-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) jerboa_qt_shim.$(SHLIB_EXT) libjsh-ffi.$(SHLIB_EXT) libjsh_coreutils_stub.$(SHLIB_EXT) jemacs_tls_stubs.$(SHLIB_EXT) $(NATIVE_LIB) $(SSL_SHIM) QT_QPA_PLATFORM=offscreen $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script tests/test-qt.ss @if [ -f tests/test-qt-part2.ss ]; then \ @@ -796,17 +814,23 @@ scintilla-vendor-deps: $(SCI_VENDOR_SCINTILLA_A) $(SCI_VENDOR_LEXILLA_A) $(SCI_V $(SCI_VENDOR_SCINTILLA_H): mkdir -p $(SCINTILLA_VENDOR) - curl -fsSL $(SCINTILLA_URL) | tar xzf - -C $(SCINTILLA_VENDOR) + @tmp=$$(mktemp "$${TMPDIR:-/tmp}/scintilla.XXXXXX"); trap 'rm -f "$$tmp"' EXIT HUP INT TERM; \ + curl -fsSL $(SCINTILLA_URL) -o "$$tmp"; \ + sh scripts/verify-sha256.sh '$(SCINTILLA_SHA256)' "$$tmp"; \ + tar xzf "$$tmp" -C $(SCINTILLA_VENDOR) $(SCI_VENDOR_LEXILLA_H): mkdir -p $(SCINTILLA_VENDOR) - curl -fsSL $(LEXILLA_URL) | tar xzf - -C $(SCINTILLA_VENDOR) + @tmp=$$(mktemp "$${TMPDIR:-/tmp}/lexilla.XXXXXX"); trap 'rm -f "$$tmp"' EXIT HUP INT TERM; \ + curl -fsSL $(LEXILLA_URL) -o "$$tmp"; \ + sh scripts/verify-sha256.sh '$(LEXILLA_SHA256)' "$$tmp"; \ + tar xzf "$$tmp" -C $(SCINTILLA_VENDOR) $(SCI_VENDOR_TB_H): $(SCI_VENDOR_SCINTILLA_H) - git clone --depth 1 $(SCINTILLA_TB_URL) $(SCI_VENDOR_TB_DIR) + sh support/fetch-locked-dependency.sh $(SCINTILLA_TB_URL) $(SCINTILLA_TERMBOX_REV) $(SCI_VENDOR_TB_DIR) $(SCI_VENDOR_TERMBOX_H): $(SCI_VENDOR_TB_H) - git clone --depth 1 $(TERMBOX_URL) $(SCI_VENDOR_TERMBOX_DIR) + sh support/fetch-locked-dependency.sh $(TERMBOX_URL) $(TERMBOX_NEXT_REV) $(SCI_VENDOR_TERMBOX_DIR) $(SCI_VENDOR_TERMBOX_A): $(SCI_VENDOR_TERMBOX_H) $(MAKE) -C $(SCI_VENDOR_TERMBOX_DIR) FLAGS="-std=c99 -pedantic -Wall -Werror -g -fPIC -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700" @@ -925,7 +949,7 @@ check-docker: # Disabled legacy container dependency image target. # Takes ~45-60 min: Qt6 static + QScintilla + Chez Scheme + all shims. -CHEZ_COMMIT ?= 902a10098603481afce0ec8588114234c09d6318 +CHEZ_COMMIT ?= $(CHEZ_REV) podman-deps: check-podman $(PODMAN) build $(PODMAN_BUILD_FLAGS) \ @@ -972,17 +996,17 @@ linux-amd64: *) $(MAKE) jemacs-linux-amd64-remote ;; \ esac -jemacs-linux-amd64-local: ensure-jerboa-tools vendor-deps +jemacs-linux-amd64-local: ensure-vendor-jerboa-tools vendor-deps $(NATIVE_LIB) @case "$$(uname -s)-$$(uname -m)" in \ Linux-x86_64|Linux-amd64) ;; \ *) echo "ERROR: run locally on Linux amd64, or use 'make jemacs-linux-amd64-remote LINUX_BUILD_HOST=host'." >&2; exit 1 ;; \ esac - JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild.linux-amd64 + JERBOA_NATIVE_A="$(abspath $(JERBOA_NATIVE_A))" JERBUILD="$(VENDORED_JERBUILD)" "$(VENDORED_JERBUILD)" build --config .jerbuild.linux-amd64 @ls -lh jemacs-linux-amd64 @file jemacs-linux-amd64 jemacs-linux-amd64-remote: - $(SSH) $(LINUX_BUILD_HOST) 'set -e; if [ -d "$(LINUX_BUILD_ROOT)/.git" ]; then cd "$(LINUX_BUILD_ROOT)" && git fetch origin && git checkout "$(REMOTE_BRANCH)" && git pull --ff-only; else mkdir -p "$$(dirname "$(LINUX_BUILD_ROOT)")" && git clone "$(REMOTE_REPO_URL)" "$(LINUX_BUILD_ROOT)" && cd "$(LINUX_BUILD_ROOT)" && git checkout "$(REMOTE_BRANCH)"; fi; make linux-amd64' + $(SSH) $(LINUX_BUILD_HOST) 'set -e; test -n "$(REMOTE_COMMIT)"; if [ -d "$(LINUX_BUILD_ROOT)/.git" ]; then cd "$(LINUX_BUILD_ROOT)"; else test ! -e "$(LINUX_BUILD_ROOT)"; mkdir -p "$(LINUX_BUILD_ROOT)"; git -C "$(LINUX_BUILD_ROOT)" init -q; git -C "$(LINUX_BUILD_ROOT)" remote add origin "$(REMOTE_REPO_URL)"; cd "$(LINUX_BUILD_ROOT)"; fi; test "$$(git remote get-url origin)" = "$(REMOTE_REPO_URL)"; git fetch --no-tags --depth 1 origin "$(REMOTE_COMMIT)"; test "$$(git rev-parse FETCH_HEAD)" = "$(REMOTE_COMMIT)"; git checkout --detach "$(REMOTE_COMMIT)"; make linux-amd64' $(SCP) $(LINUX_BUILD_HOST):$(LINUX_BUILD_ROOT)/jemacs-linux-amd64 ./jemacs-linux-amd64 @ls -lh jemacs-linux-amd64 @file jemacs-linux-amd64 @@ -994,17 +1018,17 @@ linux-arm64: $(MAKE) jemacs-linux-arm64-remote ;; \ esac -jemacs-linux-arm64-local: ensure-jerboa-tools vendor-deps +jemacs-linux-arm64-local: ensure-vendor-jerboa-tools vendor-deps $(NATIVE_LIB) @case "$$(uname -s)-$$(uname -m)" in \ Linux-aarch64|Linux-arm64) ;; \ *) echo "ERROR: run locally on Linux arm64, or set LINUX_ARM64_BUILD_HOST and use make linux-arm64." >&2; exit 1 ;; \ esac - JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild.linux-arm64 + JERBOA_NATIVE_A="$(abspath $(JERBOA_NATIVE_A))" JERBUILD="$(VENDORED_JERBUILD)" "$(VENDORED_JERBUILD)" build --config .jerbuild.linux-arm64 @ls -lh jemacs-linux-arm64 @file jemacs-linux-arm64 jemacs-linux-arm64-remote: - $(SSH) $(LINUX_ARM64_BUILD_HOST) 'set -e; if [ -d "$(LINUX_ARM64_BUILD_ROOT)/.git" ]; then cd "$(LINUX_ARM64_BUILD_ROOT)" && git fetch origin && git checkout "$(REMOTE_BRANCH)" && git pull --ff-only; else mkdir -p "$$(dirname "$(LINUX_ARM64_BUILD_ROOT)")" && git clone "$(REMOTE_REPO_URL)" "$(LINUX_ARM64_BUILD_ROOT)" && cd "$(LINUX_ARM64_BUILD_ROOT)" && git checkout "$(REMOTE_BRANCH)"; fi; make linux-arm64' + $(SSH) $(LINUX_ARM64_BUILD_HOST) 'set -e; test -n "$(REMOTE_COMMIT)"; if [ -d "$(LINUX_ARM64_BUILD_ROOT)/.git" ]; then cd "$(LINUX_ARM64_BUILD_ROOT)"; else test ! -e "$(LINUX_ARM64_BUILD_ROOT)"; mkdir -p "$(LINUX_ARM64_BUILD_ROOT)"; git -C "$(LINUX_ARM64_BUILD_ROOT)" init -q; git -C "$(LINUX_ARM64_BUILD_ROOT)" remote add origin "$(REMOTE_REPO_URL)"; cd "$(LINUX_ARM64_BUILD_ROOT)"; fi; test "$$(git remote get-url origin)" = "$(REMOTE_REPO_URL)"; git fetch --no-tags --depth 1 origin "$(REMOTE_COMMIT)"; test "$$(git rev-parse FETCH_HEAD)" = "$(REMOTE_COMMIT)"; git checkout --detach "$(REMOTE_COMMIT)"; make linux-arm64' $(SCP) $(LINUX_ARM64_BUILD_HOST):$(LINUX_ARM64_BUILD_ROOT)/jemacs-linux-arm64 ./jemacs-linux-arm64 @ls -lh jemacs-linux-arm64 @file jemacs-linux-arm64 @@ -1014,12 +1038,12 @@ jemacs-linux-arm64: linux-arm64 freebsd: freebsd-amd64 -freebsd-amd64: ensure-jerboa-tools vendor-deps +freebsd-amd64: ensure-vendor-jerboa-tools vendor-deps $(NATIVE_LIB) @case "$$(uname -s)-$$(uname -m)" in \ FreeBSD-amd64|FreeBSD-x86_64) ;; \ *) echo "ERROR: build freebsd-amd64 natively on a FreeBSD amd64 host." >&2; exit 1 ;; \ esac - JERBUILD="$(JERBUILD_TOOL)" "$(JERBUILD_TOOL)" build --config .jerbuild.freebsd-amd64 + JERBOA_NATIVE_A="$(abspath $(JERBOA_NATIVE_A))" JERBUILD="$(VENDORED_JERBUILD)" "$(VENDORED_JERBUILD)" build --config .jerbuild.freebsd-amd64 @ls -lh jemacs-freebsd-amd64 @file jemacs-freebsd-amd64 @@ -1177,15 +1201,16 @@ linux-static-qt-podman: vendor-deps ensure-podman-deps -v $(JSH_COREUTILS_LIB):/host-jsh-coreutils.a:ro \ $(AWS_PODMAN_RUN_MOUNTS) \ $(DEPS_IMAGE) \ - sh -c "apk add --no-cache libvterm-dev libvterm-static openssl-dev; \ - if [ ! -f /usr/lib/libssl.a ]; then \ - echo 'Building static OpenSSL (one-time)...' && \ - cd /tmp && wget -q https://www.openssl.org/source/openssl-3.3.2.tar.gz && \ - tar xf openssl-3.3.2.tar.gz && cd openssl-3.3.2 && \ - ./Configure linux-x86_64 no-shared no-tests no-apps -O2 --prefix=/usr && \ - make -j$(nproc) && cp libssl.a libcrypto.a /usr/lib/ && \ - cd / && rm -rf /tmp/openssl-3.3.2*; \ - fi; \ + sh -c "set -eu; apk add --no-cache libvterm-dev libvterm-static openssl-dev; \ + echo 'Building pinned static OpenSSL...' && \ + cd /tmp && wget -q https://github.com/openssl/openssl/releases/download/openssl-$(OPENSSL_VERSION)/openssl-$(OPENSSL_VERSION).tar.gz && \ + echo '$(OPENSSL_SHA256) openssl-$(OPENSSL_VERSION).tar.gz' | sha256sum -c - && \ + tar xf openssl-$(OPENSSL_VERSION).tar.gz && cd openssl-$(OPENSSL_VERSION) && \ + ./Configure no-shared no-tests -O2 --prefix=/usr && \ + make -j$$(nproc) && ./apps/openssl version | grep -F 'OpenSSL $(OPENSSL_VERSION)' && \ + ./apps/openssl version -a > /usr/lib/jemacs-openssl-build.txt && \ + cp libssl.a libcrypto.a /usr/lib/ && \ + cd / && rm -rf /tmp/openssl-$(OPENSSL_VERSION)*; \ cp /host-jsh-coreutils.a /deps/jsh/libjsh_coreutils.a; \ cp -a /host-jsh-root/. /deps/jsh/; \ mkdir -p /deps/jsh/vendor/jsqlite && cp -a /host-jsqlite/. /deps/jsh/vendor/jsqlite/; \ --- a/README.md +++ b/README.md @@ -64,8 +64,14 @@ make jerbuild-check `ensure-jerboa-tools` follows the same convention as `jerboa-code` and `jerboa-shell`: it prefers project-local tools, a sibling `../jerboa` build, or `PATH`, and otherwise downloads `jerboa-${JERBOA_VERSION}` into `.jerboa/bin`. +Downloaded toolchains are accepted only when their target-specific SHA-256 +matches `dependencies.lock`; a checksum fetched beside the artifact is not a +trust root. The `.jerbuild*` configs describe native target-host builds for macOS/Linux/ -FreeBSD without requiring an installed full Jerboa runtime. +FreeBSD without requiring an installed full Jerboa runtime. Release targets use +the exact vendored `jerbuild` and statically link its pinned +`libjerboa_native.a`; failing crypto/TLS placeholder stubs are not part of +release binaries. ### Cross and target builds @@ -101,8 +107,11 @@ jerbuild build --config .jerbuild.freebsd-amd64 make vendor-deps ``` -This clones required dependencies into `vendor/` from their SourceHut remotes. -The default `make build` target runs this first. +This fetches the exact full commits recorded in `dependencies.lock` into +`vendor/` and rejects an existing checkout at any other revision. Archive, +container, static OpenSSL 3.5.7 LTS, Chez, and Tree-sitter inputs are likewise +digest- or commit-pinned. The default `make build` target verifies this graph +first; `make vendor-update` never follows a mutable upstream head. ### Build library modules @@ -143,6 +152,15 @@ make run-qt Requires the Qt backend build (see below). +### Authenticated local control sockets + +`--repl PORT` enables the full-power loopback debug REPL. It always requires a +fresh 256-bit token, atomically published with the port in the mode-0600 +`~/.jerboa-repl-port` file. The always-on `jerboa-client` IPC endpoint uses an +independent token in `~/.jemacs-server`. Both endpoints enforce bounded input, +deadlines, and one active connection. See +[docs/repl-server.md](docs/repl-server.md) for the protocol and threat model. + ### Build Qt backend ```bash @@ -159,6 +177,9 @@ The Qt backend consists of 45 modules in `src/jerboa-emacs/qt/` (~48,801 lines). make test ``` +The suite includes hostile authentication, overlong-frame, traversal, endpoint +symlink, private-temp-file, and shell-metacharacter argv tests. + This runs: test-tier0, test-tier2, test-tier3, test-tier4, test-tier5, test-org, test-extra. ### Run specific test suites --- a/SECURITY.md +++ b/SECURITY.md @@ -9,19 +9,33 @@ production-readiness tracker gates are complete. - Source files are edited in `src/`; generated `lib/*.sls` files must stay in sync with source and must not be hand-edited. -- Production readers use `:jerboa/reader` with read-eval disabled where command - input or socket input is parsed. +- Production command/socket parsers use the bounded Jerboa reader with explicit + input, nesting, container, string, and symbol limits. Chez itself rejects + `#.` syntax and does not export a `read-eval` parameter. - File path sinks reject embedded NUL bytes before opening, deleting, loading, saving, chmoding, or shelling out on caller-provided paths. -- Shell/process helpers use fixed executables and argv-style calls where the - editor invokes host tools such as `git`, `rg`, `nc`, `chmod`, and launchers. -- The debug REPL socket is local-development tooling. Do not expose it outside a - local trust boundary, and do not enable command shells in static REPL builds - unless `JERBOA_REPL_ENABLE_SHELL=1` is an intentional operator choice. +- Shell/process helpers use absolute, metadata-checked executables and argv + vectors. Filename, remote-path, URL, and working-directory data is never + interpolated into a shell command. Explicit interactive shell commands remain + an intentional code-execution feature. +- Debug REPL and editor IPC connections use mandatory authentication with + independent 256-bit tokens, private atomic endpoint files, strict input and + queue caps, and idle/total deadlines. The full-power REPL remains + local-development tooling and must never be forwarded or proxied. +- Sudo-save staging uses unpredictable `O_EXCL|O_NOFOLLOW` files created mode + 0600, validated as regular/single-link, fsynced, and removed under guaranteed + cleanup. Privileged copies use fixed executable argv vectors. - Native FFI bindings load lazily through `(jerboa ffi)` and must not run native loader side effects at library import time. +- Native `.jerbuild*` release binaries use the locked vendored toolchain and + statically link its real Rust crypto/TLS archive; fail-only native stubs are + excluded from those release manifests. - Blocking native calls are collect-safe where they can wait on terminals, sockets, subprocesses, Qt, Scintilla, or ptys. +- `dependencies.lock` pins every cloned build input, release-toolchain digest, + source archive digest, and container digest. The static TLS build uses + OpenSSL 3.5.7 LTS and verifies the upstream archive SHA-256 before building; + the image records and checks the actual linked build version. ## Sensitive Data new file mode 100644 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,57 @@ +# Reviewed immutable Git inputs for vendor-deps. Update each revision in a +# dedicated dependency-review change; release builds fail on any mismatch. +JERBOA_REV := 37426d9bb88e0cef607141605ae4bc928d5ccb24 +JERBOA_SHELL_REV := 413c59d5ed4bb61229868793d04a7eb8cd4c4219 +JERBOA_PCRE2_REV := 719751bc7ef3e408e9da1a260393859f8bed9ea8 +JERBOA_SCINTILLA_REV := 96745c5ea69c95f1c4fa2ce80f43e9cdf59a55a6 +JERBOA_QT_REV := dc86e8354ed3406066e9ce7482c0d1d43e4c6272 +JERBOA_SSL_REV := f466a3149f6c9a0e6f8dd202e00bb746a9bacd2c +JERBOA_HTTPS_REV := 966c2f83e746e0153c7a843b235d8a2e466e1560 +JSQLITE_REV := 63d737101b3b74bff3b9db75cc35f34bff6df0b7 +JERBOA_LSP_REV := 2214f9c4e18abd714426fcf712dcfa55e66a8bf6 +JERBOA_AWS_REV := 38fd942574800c1d061dad362594bb9c8bc69727 + +JSH_COREUTILS_REV := 6cf228c84f2a7141e41df366b341423fd3f8ad09 +JSH_AWK_REV := b5bf6e69f1c7317f629ead7aa6d3cab672de919e +JSH_SED_REV := 34b3311f7fa3297afeb1cc9609029389d63ad7e4 +JSH_AWS_REV := 3f1181943c8c397d4922e5a090168dd749723528 +JSH_YUBIKEY_REV := e448afca926ca9ad40fd98effa8d6264cb1019f5 +JSH_SSH_REV := b47b3d0ee53777444de3e7b2b238d4b744058221 +JSH_FUSE_REV := 3b7b3a8d5d76100ea1526acefe7f696c96a62b2b + +# Static source archives and security-sensitive toolchain inputs. +OPENSSL_VERSION := 3.5.7 +OPENSSL_SHA256 := a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8 +LIBXAU_SHA256 := 74d0e4dfa3d39ad8939e99bda37f5967aba528211076828464d2777d477fc0fb +XCB_UTIL_SHA256 := 5abe3bbbd8e54f0fa3ec945291b7e8fa8cfd3cccc43718f8758430f94126e512 +QTBASE_SHA256 := 56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 +QSCINTILLA_SHA256 := dfe13c6acc9d85dfcba76ccc8061e71a223957a6c02f3c343b30a9d43a4cdd4d +ALPINE_3_21_IMAGE_DIGEST := sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d +UBUNTU_24_04_IMAGE_DIGEST := sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90 +JERBOA_IMAGE_DIGEST := sha256:9c4034f1296601d5a9f7b219a0decdd5d62862a2be4347976b3d96b13ed7701e