Files
gstack/test/learnings-injection.test.ts
T
Garry Tan 4bc4bfedfd fix(bin): pass search params via env vars (RCE fix) (#819)
Replace shell string interpolation with process.env in gstack-learnings-search
to prevent arbitrary code execution via crafted learnings entries. Also fixes
the CROSS_PROJECT interpolation that the original PR missed.

Adds 3 regression tests verifying no shell interpolation remains in the bun -e block.

Co-authored-by: garagon <garagon@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 11:44:07 -07:00

49 lines
2.1 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import { readFileSync } from "fs";
import path from "path";
const SCRIPT = path.join(import.meta.dir, "..", "bin", "gstack-learnings-search");
describe("gstack-learnings-search injection prevention", () => {
const script = readFileSync(SCRIPT, "utf-8");
test("no shell interpolation inside bun -e string", () => {
// Extract the bun -e block (everything between `bun -e "` and the closing `"`)
const bunBlock = script.slice(script.indexOf('bun -e "'));
// Should NOT contain ${VAR} patterns (shell interpolation)
// These are RCE vectors: a malicious learnings entry with '; rm -rf / ;' in the
// query field would execute arbitrary commands via shell interpolation.
const shellInterpolations = bunBlock.match(/'\$\{[A-Z_]+\}'/g) || [];
const bareInterpolations = bunBlock.match(/\$\{[A-Z_]+\}/g) || [];
// Filter out any that are inside process.env references (those are safe)
const unsafeInterpolations = [
...shellInterpolations,
...bareInterpolations,
].filter((m) => !m.includes("process.env"));
expect(unsafeInterpolations).toEqual([]);
});
test("uses process.env for all user-controlled values", () => {
const bunBlock = script.slice(script.indexOf('bun -e "'));
// Must use process.env for TYPE, QUERY, LIMIT, SLUG, CROSS_PROJECT
expect(bunBlock).toContain("process.env.GSTACK_SEARCH_TYPE");
expect(bunBlock).toContain("process.env.GSTACK_SEARCH_QUERY");
expect(bunBlock).toContain("process.env.GSTACK_SEARCH_LIMIT");
expect(bunBlock).toContain("process.env.GSTACK_SEARCH_SLUG");
expect(bunBlock).toContain("process.env.GSTACK_SEARCH_CROSS");
});
test("env vars are set on the bun command line", () => {
// The env vars must be passed to bun, not just set in the shell
expect(script).toContain("GSTACK_SEARCH_TYPE=");
expect(script).toContain("GSTACK_SEARCH_QUERY=");
expect(script).toContain("GSTACK_SEARCH_LIMIT=");
expect(script).toContain("GSTACK_SEARCH_SLUG=");
expect(script).toContain("GSTACK_SEARCH_CROSS=");
});
});