From 65d5c5bcb401793cd562676e3e7d31f3f9322cb5 Mon Sep 17 00:00:00 2001 From: Maoyin Sun Date: Sun, 26 Jul 2026 21:25:16 +0200 Subject: [PATCH 1/2] docs: bring CHANGELOG [Unreleased] up to date with this pass The changelog had fallen behind the recent fixes. Record, under the existing categories: Security: empty ELL_API_KEY sends no auth header (was an empty Bearer/ api-key that also tripped the http:// guard); bracketed IPv6 loopback ([::1], with/without port) recognised as loopback; auth --config file escapes backslashes as well as quotes. Fixed: plugin hooks in spaced paths now also *execute* (piping quotes them), not just discovered; non-streaming path propagates curl's real exit code; interactive prompt uses printf not echo -ne; here-string (not process substitution) for hook discovery and correct dispatcher dir under bash 4.1; BSD/macOS awk/sed/script fixes; Windows file:// path rewrite. Performance: plugin per-character loops use bash builtins. Added: macOS + Git Bash/MSYS2 CI with runtime capability SKIPs; suite no longer needs cmp/diffutils; Windows limitations documented. --- CHANGELOG.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a3797a..d18495c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,17 @@ This is a large hardening pass focused on security, robustness, and testing. `--config` file (mode 0600, removed after use) instead of a `--header` argument, and it is no longer logged at debug level. - ell refuses to send credentials to a plaintext `http://` URL (loopback - excepted); override with `ELL_ALLOW_INSECURE_URL=true`. + excepted); override with `ELL_ALLOW_INSECURE_URL=true`. Bracketed IPv6 + loopback (`http://[::1]`, with or without a port) is correctly recognised as + loopback and allowed. +- When `ELL_API_KEY` is empty the backends now send **no** auth header at all, + instead of an empty `Authorization: Bearer ` / `x-goog-api-key:`. This matches + the preflight (which permits running with no key) and avoids the empty header + wrongly tripping the "refuse credential over `http://`" guard. +- The auth header written to curl's `--config` file now escapes backslashes as + well as double quotes, so a key/header containing a backslash is passed to + curl intact instead of being mangled (a lone `\t` etc. was interpreted by + curl's config parser). - The bundled redaction plugin now ships **disabled by default** (its hook has a `.disabled` suffix); enable it by removing the suffix. @@ -54,15 +64,34 @@ This is a large hardening pass focused on security, robustness, and testing. - Record mode re-execs ell by an absolute, shell-quoted path, so it works when ell is run in place or is not on `PATH`. - Template resolution rejects path-traversal names and accepts an - `ELL_TEMPLATE_PATH` with or without a trailing slash; plugin-hook discovery no - longer breaks on paths containing spaces/newlines. + `ELL_TEMPLATE_PATH` with or without a trailing slash. Plugin hooks in paths + containing spaces/newlines are handled end to end: both discovered + (`list_plugin_hooks`) and executed -- `piping` now shell-quotes a hook stage + whose path contains spaces instead of word-splitting it into a "No such file" + failure. - Missing `ELL_API_URL` now fails early with a clear, actionable message (EX_CONFIG) instead of an opaque curl error; a missing `ELL_API_KEY` warns. - curl failures are reported with a human-readable explanation (DNS, connection - refused, timeout, TLS, …) rather than a bare exit code. + refused, timeout, TLS, …) rather than a bare exit code. The non-streaming path + now propagates curl's real exit code on a transport failure (as the streaming + path and `docs/Backends.md` already promised) instead of collapsing it to `1`. +- The interactive prompt is printed with `printf` instead of `echo -ne`, whose + `-n`/`-e` handling is unreliable across shells (`ELL_PS1` already holds real + escape bytes, matching how `ELL_PS2` is printed). - Functions are exported with `export -f` rather than as empty variables. - Bash 4.1 compatibility is restored (the version gate, docs and CI now - consistently target 4.1). + consistently target 4.1). Plugin-hook discovery uses a here-string rather than + process substitution (`< <(…)`), which is a parse error under `set -o posix` + on bash 4.1; and the backend dispatcher resolves its directory correctly when + sourced directly (with `BASE_DIR` unset), not only when launched via ell.sh. +- BSD/macOS toolchain fixes: the syntax-highlight plugin no longer spins into an + infinite loop under BSD `awk` (it used `awk -F ''`, a GNU-only extension) and + now uses bash builtins; the redaction plugin's word boundaries work under BSD + `sed` (which lacks `\b`); and record mode uses the BSD `script(1)` command + syntax on macOS (`script … file command`) rather than the GNU `-c` form. +- Windows (Git Bash / MSYS) fix: a `file://` URL built from a POSIX path is + rewritten to the `file:///C:/…` form the native curl understands, so the + bundled `file://` backend and the offline tests work there. ### Changed @@ -85,6 +114,9 @@ This is a large hardening pass focused on security, robustness, and testing. re-parsing the whole accumulated buffer per line (was O(n²)). - Streaming loops use bash builtins instead of a `grep`/`cut`/`tr` subprocess per line, and logging no longer forks `date`/`basename` per line. +- The syntax-highlight and paginator plugins' per-character loops likewise use + bash pattern matching / parameter expansion instead of forking `echo | grep`, + `echo | cut` or `awk` for each character. ### Added @@ -95,6 +127,17 @@ This is a large hardening pass focused on security, robustness, and testing. syntax-highlight and paginator plugins; and end-to-end tests for the request pipeline, hook stages, interactive mode, output redirection, error paths, the launcher, record mode, the bash version gate and terminal-size fallback. +- CI now also runs the suite on macOS (BSD toolchain) and on Windows under both + Git Bash and a fuller MSYS2 environment. Tests that depend on a facility a + platform lacks (POSIX permission bits, real symlinks, `script(1)`, an + isolable `bash`) detect that at runtime and `SKIP` instead of failing. +- The test suite no longer depends on `cmp(1)`/diffutils for byte-exact + comparisons (it uses a pure-bash comparison), so it runs on minimal + environments; and CI does not install diffutils, keeping that guarantee under + test. +- Documentation: a "Windows" section in `docs/Configuration.md` and a README + note spelling out Git Bash's limitations (the config-trust check and the + `0600` credential file are not enforceable there) and recommending WSL/MSYS2. ## [0.1.1] From c413ce6f3f64fcf0f804c6615bf69f8899d571dd Mon Sep 17 00:00:00 2001 From: Maoyin Sun Date: Sun, 26 Jul 2026 21:30:07 +0200 Subject: [PATCH 2/2] release: v0.2.0 Bump ELL_VERSION to 0.2.0 and finalize the changelog: promote the [Unreleased] entries to [0.2.0] (2026-07-26), keep an empty [Unreleased] above it, and add the version comparison links. Update the version-test fallback to match. 0.2.0 (minor) covers the security/robustness/testing hardening pass plus cross-platform support (BSD/macOS and Windows Git Bash/MSYS2). --- CHANGELOG.md | 6 ++++++ ell.sh | 2 +- tests/bash_version.sh | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d18495c..f015a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.2.0] - 2026-07-26 + This is a large hardening pass focused on security, robustness, and testing. ### Security @@ -142,3 +144,7 @@ This is a large hardening pass focused on security, robustness, and testing. ## [0.1.1] - Baseline release prior to the hardening pass above. + +[Unreleased]: https://github.com/simonmysun/ell/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/simonmysun/ell/compare/v0.1.1...v0.2.0 +[0.1.1]: https://github.com/simonmysun/ell/releases/tag/v0.1.1 diff --git a/ell.sh b/ell.sh index 2d12319..ab94450 100755 --- a/ell.sh +++ b/ell.sh @@ -11,7 +11,7 @@ else exit 69; fi -ELL_VERSION="0.1.1"; +ELL_VERSION="0.2.0"; : "${ELL_LOG_LEVEL:=2}"; diff --git a/tests/bash_version.sh b/tests/bash_version.sh index 29c5ca6..cd209fc 100644 --- a/tests/bash_version.sh +++ b/tests/bash_version.sh @@ -54,7 +54,7 @@ assert_failure "0 rejected" version_ok 0 0; ver_out="$("${DIR}/../ell" --version 2>&1)"; ver_status="${?}"; assert_equals "ell --version exits 0 on supported bash" "0" "${ver_status}"; -assert_contains "ell --version prints the version" "${ver_out}" "${ELL_VERSION:-0.1.1}"; +assert_contains "ell --version prints the version" "${ver_out}" "${ELL_VERSION:-0.2.0}"; assert_not_contains "supported bash is not rejected" "${ver_out}" "is required to run"; # The rejection message (as written in ell.sh) names 4.1, matching README/CI.