mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-15 09:25:28 +02:00
fix(redact): prepush guard fails closed on git failure; /ship owns hook install (#1946)
Two gaps closed:
1. Fail closed. The git() helper returned "" on ANY non-zero exit or
maxBuffer overflow (status null), addedLinesFor produced an empty
string, and the push sailed through unscanned — fail-open on exactly
the oversized-diff case where a large secret-bearing blob is most
likely. The diff call now uses a strict variant that throws; main
blocks with a clear message naming the GSTACK_REDACT_PREPUSH=skip
escape valve. Probe calls (symbolic-ref, rev-parse, merge-base) keep
the permissive helper — their failures are normal control flow.
2. Install path. The hook was installed by nothing ("opt-in, installed by
nothing" was the issue's words). ./setup runs in the gstack checkout —
the wrong repo for a per-project hook — so it gets a one-line hint
only. /ship owns per-repo install: config redact_prepush_hook=true +
hook missing → silent install (consent already given); config unset +
no ~/.gstack/.redact-prepush-prompted marker → one-time machine-wide
AskUserQuestion offer, answer persisted. ship/SKILL.md regenerated in
this same commit (check-freshness bisect discipline).
Tests: unscannable diff (bogus SHAs) → exit 1 + valve named; empty-but-
successful diff → exit 0; static asserts pin setup as hint-only and the
ship template as the installer surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c8f078b482
commit
6bc00abb0f
@@ -107,6 +107,46 @@ describe("diff direction + special refs", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("fail closed on unscannable diffs (#1946)", () => {
|
||||
test("a diff git cannot compute BLOCKS the push and names the escape valve", () => {
|
||||
// Bogus-but-well-formed SHAs: git diff exits non-zero, the old git()
|
||||
// helper returned "" and the push sailed through unscanned.
|
||||
const bogusLocal = "a".repeat(40);
|
||||
const bogusRemote = "b".repeat(40);
|
||||
const { code, stderr } = runHook(
|
||||
`refs/heads/main ${bogusLocal} refs/heads/main ${bogusRemote}\n`,
|
||||
);
|
||||
expect(code).toBe(1);
|
||||
expect(stderr).toContain("could not compute the pushed diff");
|
||||
expect(stderr).toContain("GSTACK_REDACT_PREPUSH=skip");
|
||||
});
|
||||
|
||||
test("an empty-but-successful diff still passes (no-op push)", () => {
|
||||
const head = git(["rev-parse", "HEAD"]);
|
||||
// remote == local: diff succeeds and is empty — must NOT block.
|
||||
const { code } = runHook(`refs/heads/main ${head} refs/heads/main ${head}\n`);
|
||||
expect(code).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("install UX surfaces (#1946 / eng review D3+D10)", () => {
|
||||
const ROOT = path.resolve(import.meta.dir, "..");
|
||||
|
||||
test("setup carries the hint only — never a per-repo install (it runs in the wrong repo)", () => {
|
||||
const setup = fs.readFileSync(path.join(ROOT, "setup"), "utf8");
|
||||
expect(setup).toContain("redact_prepush_hook");
|
||||
// The hint must not invoke the installer from setup.
|
||||
expect(setup).not.toContain("install-prepush-hook");
|
||||
});
|
||||
|
||||
test("ship template owns per-repo install: silent-install path + one-time offer marker", () => {
|
||||
const tmpl = fs.readFileSync(path.join(ROOT, "ship", "SKILL.md.tmpl"), "utf8");
|
||||
expect(tmpl).toContain("install-prepush-hook");
|
||||
expect(tmpl).toContain(".redact-prepush-prompted");
|
||||
expect(tmpl).toContain("redact_prepush_hook");
|
||||
});
|
||||
});
|
||||
|
||||
describe("escape valve", () => {
|
||||
test("GSTACK_REDACT_PREPUSH=skip bypasses + logs", () => {
|
||||
const base = git(["rev-parse", "HEAD"]);
|
||||
|
||||
Reference in New Issue
Block a user