Security
Source-level audit summary. The full
AUDIT-2026-07-15.md lives in the repo.
Summary
| # | level | area | title |
|---|---|---|---|
| 1 | HIGH | exec | popen("diff", ...) PATH lookup, mitigated in dist build |
| 2 | HIGH | exec | popen($PAGER, ...) follows $PAGER, documented |
| 3 | MEDIUM | tempfile | Temp files default to /tmp (privacy leak) |
| 4 | LOW | ctype | isspace(int) on getc() return without cast |
| 5 | LOW | alloc | asprintf overwrites its input pointer (small leak) |
| 6–9 | INFO | — | locale-dependent isspace, signal handler, no network, diffutils 3.10 baseline |
Finding #1 (HIGH) — popen("diff", ...) PATH lookup
Where. src/wdiff.c:1005-1019
launch_input_program calls
readpipe(DIFF_PROGRAM, ...) where
DIFF_PROGRAM is the literal string
"diff" by default.
Effect. wdiff runs diff via
$PATH lookup. If PATH is hostile
(e.g. PATH=/tmp:$PATH and an attacker has
written /tmp/diff), wdiff runs the attacker's
binary. The attacker's binary can do anything the calling
user can. This is the same threat model as
git, less, and every other tool
that uses popen + PATH lookup.
Mitigation in the ljh-sh dist.
scripts/build.sh passes
DIFF_PROGRAM as an absolute path
pointing to the bundled diff binary in the same
dist archive. The runtime binary never traverses
$PATH. The smoke.sh test
verifies this by setting PATH=/tmp/with-sentinel:$PATH
and asserting that the sentinel script is never invoked.
Finding #2 (HIGH) — popen($PAGER, ...)
Where. src/wdiff.c:1051-1105
launch_output_program.
Effect. When stdout is a tty and -a
autopager is on, wdiff invokes the user's
PAGER (WDIFF_PAGER first, then
PAGER, then a compile-time default). This is
the documented behaviour of $PAGER in many
tools. The ljh-sh dist does not enable autopager by default
in any future --agent mode; users who want
autopager accept the same risk they already have via
man, git log, etc.
Finding #3 (MEDIUM) — temp files default to /tmp
Effect. wdiff uses mkstemp() (race-free
random suffix), but always in /tmp. On a
multi-user system a local attacker can
inotify /tmp/wdiff* and observe wdiff being
run — privacy leak, not code execution.
Suggested fix. Patch
create_template_filename in wdiff to honor
$TMPDIR first (already done at line 542) and
verify $TMPDIR on the build host. Deferred to
a future build-script patch.
Dist-only mitigations already applied
- Absolute
DIFF_PROGRAM— closes finding #1 for the dist binary. Smoke-tested inscripts/smoke.shsection 3. - No PAGER by default — autopager is opt-in
via
-a. Closes the threat-model surface for users who don't use-a. - 5-target CI matrix — each release is rebuilt from scratch on every push + every tag. No cached binary artefacts cross the trust boundary.
- Vendored at
wdiff 1.2.2+diffutils 3.10— the latest stable versions of both, inheriting upstream security fixes. Re-vendor (or replace withgit subtree pull) when either ships a security release.