fix: bin writers drop data on Windows paths with an apostrophe

Two independent Windows git-bash bugs in the bin writers, both silent
because callers invoke these scripts with 2>/dev/null and do not check
the exit status — a hard failure was indistinguishable from success.

Bug 1 — apostrophe in the checkout path breaks the bun -e program.
gstack-learnings-log, gstack-question-log and gstack-telemetry-log build
a bun -e program as a double-quoted shell string and interpolate
SCRIPT_DIR into a single-quoted JS import specifier. A path such as
C:/Users/Someone's PC/... closes the JS string literal early and Bun
fails to parse ("Expected ; but found s"). Every learning write and every
plan-tune question event no-oped; telemetry error redaction fell to its
fail-closed null path. The #1950 cygpath -m guard did not cover this —
cygpath normalises the drive form but does not remove the apostrophe.

Fixed by not interpolating the path at all: cd into the module root and
use a relative import specifier, which is immune to apostrophes, spaces,
backslashes and MSYS paths alike. The one remaining interpolated data
path in gstack-developer-profile (readFileSync of PROFILE_FILE) is passed
via the environment instead, matching do_log_session in the same file.

Bug 2 — gstack-developer-profile --derive fails on an MSYS-form
GSTACK_HOME. GSTACK_HOME defaults to $HOME/.gstack, which under git-bash
is /c/Users/..., and Bun on Windows cannot open that form (ENOENT). This
script carried no cygpath guard at all. Fixed by normalising GSTACK_HOME
once, before PROFILE_FILE / LEGACY_FILE / the events path are derived
from it, so all three pick up the normalised value.

Adds test/hostile-path-writers.test.ts, which runs the bins from a
directory whose name contains an apostrophe and asserts that rows are
ACTUALLY WRITTEN (not merely that the exit code is 0 — exit-code-only
checks are what masked bug 1). The apostrophe repro is OS-independent:
SCRIPT_DIR derives from the script's own location, so a copied checkout
under a hostile directory name reproduces bug 1 on Linux/macOS CI too.

Wave-amended: all four writers unified on the env-var import pattern the PR already used in gstack-developer-profile (no CWD-dependent module resolution)
Wave-amended: all four writers unified on the env-var import pattern the PR already used in gstack-developer-profile (apostrophe-safe without CWD-dependent module resolution); import-shape pin updated
This commit is contained in:
Shreshth Kapoor
2026-08-31 21:01:51 +00:00
committed by Garry Tan
parent b8f21ff713
commit 9f4e8eef48
6 changed files with 119 additions and 8 deletions
+7 -1
View File
@@ -284,7 +284,13 @@ describe('gstack-question-log — injection defense', () => {
describe('gstack-question-log — shared injection patterns (#1934 dedup)', () => {
test('imports hasInjection from lib/jsonl-store.ts instead of a local duplicate', () => {
const source = fs.readFileSync(BIN, 'utf-8');
expect(source).toContain("import { hasInjection } from '$SCRIPT_DIR/../lib/jsonl-store.ts'");
// #2720 absorption: the lib path travels via env var (apostrophe-safe —
// shell interpolation into a JS string literal broke on paths containing
// '), so the import is dynamic. The invariant is unchanged: the shared
// audited hasInjection from lib/jsonl-store.ts, never a local duplicate.
expect(source).toContain(
"const { hasInjection } = await import(process.env.GSTACK_LIB_DIR + '/jsonl-store.ts');",
);
expect(source).not.toContain('const INJECTION_PATTERNS');
});
});