mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
test(redact): fixture git and hook installs share gitEnv(), so a global core.hooksPath is never rewritten (#3353) (#3384)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e2fffee311
commit
0ea3dc8986
10 files changed
+127
-75
No files matched your search
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## [1.91.209.0] - 2026-10-25
|
||||
|
||||
**The free test suite no longer rewrites a contributor's global git pre-push hook (#3353).** On a machine with a global `core.hooksPath`, `test/redact-prepush-target.test.ts` and `test/fs-utils.test.ts` ran `gstack-redact install-prepush-hook` with the parent environment. Git resolved the hooks directory to the global one, so the install moved the developer's own `pre-push` to `pre-push.local` and left a managed wrapper pointing into the test checkout, which breaks every later push once that checkout is gone. The same config also failed 4 tests in the target file, 5 in `test/redact-prepush-hook.test.ts` and 1 in `fs-utils`. CI has no global hooks path, so it stayed green.
|
||||
|
||||
### Fixed
|
||||
- **`test/helpers/git-env.ts`** (new): `gitEnv()` is the one fixture environment for git and hook installs: `GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`, `GIT_CONFIG_COUNT=0` and no `GIT_CONFIG_PARAMETERS`. Config written inside the fixture repo still applies.
|
||||
- `test/redact-prepush-target.test.ts`, `test/redact-prepush-hook.test.ts` and `test/fs-utils.test.ts` pass it to every fixture git, install and hook spawn. `test/redact-prepush-scan-range.test.ts` and `test/redact-prepush-rebase-force-push.test.ts` drop their private copies (#2967) and import it.
|
||||
|
||||
### Tests
|
||||
- A new case in `test/redact-prepush-target.test.ts` sets a global, system and environment `core.hooksPath` to a hook that fails, builds the fixture, and checks the developer's hooks directory is untouched, the managed hook landed in the fixture repo, and a push never runs the developer's hook. It fails without the shared environment.
|
||||
- Rerun of the issue's repro with `HOME` pointing at a gitconfig with a global hooks dir: on main, two files rewrote it and three files had failures; with this change all six `redact-prepush-*` and `fs-utils` files pass and the directory is unchanged.
|
||||
|
||||
## [1.91.208.0] - 2026-10-25
|
||||
|
||||
**The Capy install guide says that the team-mode session-start updater also waits for live workflow leases.** With #3385 (#3363), `bin/gstack-session-update` skips its background fast-forward while another session holds a live workflow lease, the same leases `bin/gstack-upgrade` already honors. `docs/capy-install.md` described only the upgrade side.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.91.208.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.91.209.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.91.208",
|
||||
"version": "1.91.209",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { spawnSync } from "child_process";
|
||||
import { gitEnv } from "./helpers/git-env";
|
||||
import { mkdirpSync } from "../lib/fs-utils";
|
||||
|
||||
const REDACT = path.resolve(import.meta.dir, "..", "bin", "gstack-redact");
|
||||
@@ -93,7 +94,7 @@ describe("install-prepush-hook under bun-on-Windows EEXIST semantics (#2635)", (
|
||||
const base = tmpdir();
|
||||
try {
|
||||
const repo = path.join(base, "repo");
|
||||
spawnSync("git", ["init", "-q", repo], { timeout: 30_000 });
|
||||
spawnSync("git", ["init", "-q", repo], { timeout: 30_000, env: gitEnv() });
|
||||
const hookDir = path.join(repo, ".git", "hooks");
|
||||
fs.mkdirSync(hookDir, { recursive: true });
|
||||
const hookPath = path.join(hookDir, "pre-push");
|
||||
@@ -106,6 +107,7 @@ describe("install-prepush-hook under bun-on-Windows EEXIST semantics (#2635)", (
|
||||
cwd: repo,
|
||||
encoding: "utf8",
|
||||
timeout: 30_000,
|
||||
env: gitEnv(),
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stderr ?? "").not.toContain("EEXIST");
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Environment for fixture git commands and `gstack-redact install-prepush-hook`.
|
||||
*
|
||||
* With the parent environment, a developer's global or system core.hooksPath
|
||||
* (or the same key via GIT_CONFIG_COUNT / GIT_CONFIG_PARAMETERS) reaches the
|
||||
* fixture: `git rev-parse --git-path hooks` resolves to that directory, so the
|
||||
* install rewrites the developer's own pre-push (#3353), and their hook runs
|
||||
* on fixture pushes and rejects the local remote (#2967). /dev/null drops the
|
||||
* global file on every platform: Git for Windows rejects NUL (see
|
||||
* EMPTY_GLOBAL_GIT_CONFIG in lib/cso/process.ts). Config written inside the
|
||||
* fixture repo still applies.
|
||||
*/
|
||||
export function gitEnv(extra: Record<string, string | undefined> = {}): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = { ...process.env, ...extra };
|
||||
env.GIT_CONFIG_GLOBAL = "/dev/null";
|
||||
env.GIT_CONFIG_NOSYSTEM = "1";
|
||||
env.GIT_CONFIG_COUNT = "0";
|
||||
delete env.GIT_CONFIG_PARAMETERS;
|
||||
return env;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { spawnSync } from "child_process";
|
||||
import { gitEnv } from "./helpers/git-env";
|
||||
|
||||
const PREPUSH = path.resolve(import.meta.dir, "..", "bin", "gstack-redact-prepush");
|
||||
const REDACT = path.resolve(import.meta.dir, "..", "bin", "gstack-redact");
|
||||
@@ -21,7 +22,7 @@ let repo: string;
|
||||
let stateHome: string;
|
||||
|
||||
function git(args: string[], cwd = repo): string {
|
||||
const r = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 30_000 });
|
||||
const r = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
return r.stdout?.trim() ?? "";
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ function runHook(
|
||||
cwd: repo,
|
||||
input: Buffer.from(stdinLines),
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, ...env },
|
||||
env: gitEnv(env),
|
||||
timeout: 30_000,
|
||||
});
|
||||
return { code: r.status ?? 0, stderr: r.stderr ?? "" };
|
||||
@@ -439,7 +440,7 @@ describe("install UX surfaces (#1946 / eng review D3+D10)", () => {
|
||||
const r = spawnSync("bash", ["-c", script], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"], // stdin not a TTY
|
||||
env: { ...process.env, GSTACK_HOME: home },
|
||||
env: gitEnv({ GSTACK_HOME: home }),
|
||||
timeout: 15_000,
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
@@ -467,7 +468,7 @@ describe("install UX surfaces (#1946 / eng review D3+D10)", () => {
|
||||
const r = spawnSync("bash", ["-c", script], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: { ...process.env, GSTACK_HOME: home },
|
||||
env: gitEnv({ GSTACK_HOME: home }),
|
||||
timeout: 15_000,
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
@@ -502,7 +503,7 @@ describe("install / chaining", () => {
|
||||
const existing = path.join(hookDir, "pre-push");
|
||||
fs.writeFileSync(existing, "#!/usr/bin/env bash\necho mine\n", { mode: 0o755 });
|
||||
|
||||
const r = spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const r = spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(r.status).toBe(0);
|
||||
const installed = fs.readFileSync(existing, "utf8");
|
||||
expect(installed).toContain("gstack-redact pre-push (managed)");
|
||||
@@ -516,7 +517,7 @@ describe("install / chaining", () => {
|
||||
test("chained pre-push.local receives the final ref line (trailing newline preserved)", () => {
|
||||
const hookDir = path.join(repo, ".git", "hooks");
|
||||
fs.mkdirSync(hookDir, { recursive: true });
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, timeout: 30_000 });
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, timeout: 30_000, env: gitEnv() });
|
||||
|
||||
const seen = path.join(repo, "seen.txt");
|
||||
fs.writeFileSync(
|
||||
@@ -531,7 +532,7 @@ describe("install / chaining", () => {
|
||||
cwd: repo,
|
||||
input: Buffer.from(line),
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, GSTACK_REDACT_PREPUSH: "skip", GSTACK_HOME: stateHome },
|
||||
env: gitEnv({ GSTACK_REDACT_PREPUSH: "skip", GSTACK_HOME: stateHome }),
|
||||
timeout: 30_000,
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
@@ -545,7 +546,7 @@ describe("install / chaining", () => {
|
||||
test("a blocking pre-push.local still short-circuits the push", () => {
|
||||
const hookDir = path.join(repo, ".git", "hooks");
|
||||
fs.mkdirSync(hookDir, { recursive: true });
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, timeout: 30_000 });
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, timeout: 30_000, env: gitEnv() });
|
||||
fs.writeFileSync(
|
||||
path.join(hookDir, "pre-push.local"),
|
||||
"#!/usr/bin/env bash\nwhile read -r _a _b _c _d || [ -n \"${_a:-}\" ]; do exit 1; done\nexit 0\n",
|
||||
@@ -555,7 +556,7 @@ describe("install / chaining", () => {
|
||||
cwd: repo,
|
||||
input: Buffer.from(`refs/heads/main ${"b".repeat(40)} refs/heads/main ${ZERO}\n`),
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, GSTACK_REDACT_PREPUSH: "skip", GSTACK_HOME: stateHome },
|
||||
env: gitEnv({ GSTACK_REDACT_PREPUSH: "skip", GSTACK_HOME: stateHome }),
|
||||
timeout: 30_000,
|
||||
});
|
||||
expect(r.status).toBe(1);
|
||||
@@ -603,6 +604,7 @@ describe("install / chaining", () => {
|
||||
timeout: 30_000,
|
||||
cwd: repo,
|
||||
encoding: "utf8",
|
||||
env: gitEnv(),
|
||||
});
|
||||
expect(r.status).toBe(0);
|
||||
expect(r.stdout).toContain("refreshed stale managed pre-push hook");
|
||||
@@ -618,7 +620,7 @@ describe("install / chaining", () => {
|
||||
cwd: repo,
|
||||
input: Buffer.from(`refs/heads/main ${sha} refs/heads/main ${ZERO}\n`),
|
||||
encoding: "utf8",
|
||||
env: { ...process.env, GSTACK_REDACT_PREPUSH: "skip", GSTACK_HOME: stateHome },
|
||||
env: gitEnv({ GSTACK_REDACT_PREPUSH: "skip", GSTACK_HOME: stateHome }),
|
||||
});
|
||||
expect(run.status).toBe(0);
|
||||
expect(fs.readFileSync(path.join(stateHome, "security", "prepush-skip.jsonl"), "utf8")).toContain("env-skip");
|
||||
@@ -630,13 +632,14 @@ describe("install / chaining", () => {
|
||||
fs.mkdirSync(hookDir, { recursive: true });
|
||||
const hook = path.join(hookDir, "pre-push");
|
||||
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo });
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, env: gitEnv() });
|
||||
const first = fs.readFileSync(hook, "utf8");
|
||||
const stamp = fs.statSync(hook).mtimeMs;
|
||||
|
||||
const again = spawnSync("bun", [REDACT, "install-prepush-hook"], {
|
||||
cwd: repo,
|
||||
encoding: "utf8",
|
||||
env: gitEnv(),
|
||||
});
|
||||
expect(again.status).toBe(0);
|
||||
expect(again.stdout).toContain("already installed");
|
||||
@@ -651,8 +654,8 @@ describe("install / chaining", () => {
|
||||
fs.writeFileSync(path.join(hookDir, "pre-push"), "#!/usr/bin/env bash\necho mine\n", {
|
||||
mode: 0o755,
|
||||
});
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, timeout: 30_000 });
|
||||
spawnSync("bun", [REDACT, "uninstall-prepush-hook"], { cwd: repo, timeout: 30_000 });
|
||||
spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, timeout: 30_000, env: gitEnv() });
|
||||
spawnSync("bun", [REDACT, "uninstall-prepush-hook"], { cwd: repo, timeout: 30_000, env: gitEnv() });
|
||||
const restored = fs.readFileSync(path.join(hookDir, "pre-push"), "utf8");
|
||||
expect(restored).toContain("echo mine");
|
||||
expect(restored).not.toContain("managed");
|
||||
@@ -671,7 +674,7 @@ describe("base resolution when the default branch is neither main nor master", (
|
||||
// blocks the push having scanned NOTHING — the "scans more, never less"
|
||||
// fallback inverting into "scans nothing".
|
||||
const bare = fs.mkdtempSync(path.join(os.tmpdir(), "prepush-remote-"));
|
||||
spawnSync("git", ["init", "-q", "--bare", "-b", "trunk", bare], { timeout: 30_000 });
|
||||
spawnSync("git", ["init", "-q", "--bare", "-b", "trunk", bare], { timeout: 30_000, env: gitEnv() });
|
||||
|
||||
git(["branch", "-M", "trunk"]);
|
||||
const old = commit("legacy.txt", FAKE_AWS_KEY + "\n", "secret already on the remote");
|
||||
|
||||
@@ -31,23 +31,13 @@ import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { spawnSync } from "child_process";
|
||||
import { gitEnv } from "./helpers/git-env";
|
||||
|
||||
const PREPUSH = path.resolve(import.meta.dir, "..", "bin", "gstack-redact-prepush");
|
||||
|
||||
let repo: string;
|
||||
let remote: string;
|
||||
|
||||
// Same blanking as test/redact-prepush-scan-range.test.ts (#2967). A
|
||||
// hooksPath written into the fixture repo still runs.
|
||||
function gitEnv(): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||
env.GIT_CONFIG_GLOBAL = "/dev/null";
|
||||
env.GIT_CONFIG_NOSYSTEM = "1";
|
||||
env.GIT_CONFIG_COUNT = "0";
|
||||
delete env.GIT_CONFIG_PARAMETERS;
|
||||
return env;
|
||||
}
|
||||
|
||||
function git(args: string[], cwd = repo): string {
|
||||
const r = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
if (r.status !== 0) throw new Error(`git ${args.join(" ")}\n${r.stderr}`);
|
||||
|
||||
@@ -17,25 +17,11 @@
|
||||
*/
|
||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||
import { spawnSync } from "child_process";
|
||||
import { gitEnv } from "./helpers/git-env";
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
// Fixture git was spawned with the parent environment. A global or system
|
||||
// core.hooksPath, or the same key via GIT_CONFIG_COUNT /
|
||||
// GIT_CONFIG_PARAMETERS, runs on these pushes and rejects the local remote
|
||||
// (#2967). /dev/null drops the global file on every platform: Git for
|
||||
// Windows rejects NUL (see EMPTY_GLOBAL_GIT_CONFIG in lib/cso/process.ts).
|
||||
// Config written inside the fixture repo still applies.
|
||||
function gitEnv(): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||
env.GIT_CONFIG_GLOBAL = "/dev/null";
|
||||
env.GIT_CONFIG_NOSYSTEM = "1";
|
||||
env.GIT_CONFIG_COUNT = "0";
|
||||
delete env.GIT_CONFIG_PARAMETERS;
|
||||
return env;
|
||||
}
|
||||
|
||||
let dir: string;
|
||||
const run = (args: string[], cwd = dir): string => {
|
||||
const r = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
|
||||
@@ -4,13 +4,14 @@ import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { scan } from "../lib/redact-engine";
|
||||
import { gitEnv } from "./helpers/git-env";
|
||||
|
||||
const REDACT = path.resolve(import.meta.dir, "../bin/gstack-redact");
|
||||
const roots: string[] = [];
|
||||
const key = ["AKIA", "1234567890ABCDEF"].join("");
|
||||
|
||||
function git(cwd: string, ...args: string[]): string {
|
||||
const result = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 30_000 });
|
||||
const result = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
if (result.status !== 0) throw new Error(`git ${args[0]} failed: ${result.stderr}`);
|
||||
return result.stdout.trim();
|
||||
}
|
||||
@@ -35,7 +36,7 @@ function fixture(initialSecret = true, sha256 = false): { repo: string; origin:
|
||||
git(repo, "remote", "add", "origin", origin);
|
||||
git(repo, "push", "-q", "-u", "origin", "main");
|
||||
git(repo, "remote", "add", "publish", publish);
|
||||
const install = spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const install = spawnSync("bun", [REDACT, "install-prepush-hook"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(install.status).toBe(0);
|
||||
return { repo, origin, publish, head };
|
||||
}
|
||||
@@ -58,11 +59,49 @@ afterEach(() => {
|
||||
for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("fixture installs ignore the developer's hook config (#3353)", () => {
|
||||
test("a global, system or environment core.hooksPath is neither rewritten nor run", () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "prepush-target-global-"));
|
||||
roots.push(root);
|
||||
const hooks = path.join(root, "global-hooks");
|
||||
fs.mkdirSync(hooks);
|
||||
const original = "#!/bin/sh\necho developer-hook >&2\nexit 1\n";
|
||||
fs.writeFileSync(path.join(hooks, "pre-push"), original, { mode: 0o755 });
|
||||
const config = path.join(root, "gitconfig");
|
||||
fs.writeFileSync(config, `[core]\n\thooksPath = ${JSON.stringify(hooks)}\n`);
|
||||
const keys = ["GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM", "GIT_CONFIG_NOSYSTEM", "GIT_CONFIG_COUNT",
|
||||
"GIT_CONFIG_KEY_0", "GIT_CONFIG_VALUE_0", "GIT_CONFIG_PARAMETERS"] as const;
|
||||
const prev = Object.fromEntries(keys.map(key => [key, process.env[key]]));
|
||||
try {
|
||||
process.env.GIT_CONFIG_GLOBAL = config;
|
||||
process.env.GIT_CONFIG_SYSTEM = config;
|
||||
delete process.env.GIT_CONFIG_NOSYSTEM;
|
||||
process.env.GIT_CONFIG_COUNT = "1";
|
||||
process.env.GIT_CONFIG_KEY_0 = "core.hooksPath";
|
||||
process.env.GIT_CONFIG_VALUE_0 = hooks;
|
||||
process.env.GIT_CONFIG_PARAMETERS = `'core.hooksPath=${hooks}'`;
|
||||
const { repo } = fixture(false);
|
||||
expect(fs.readdirSync(hooks)).toEqual(["pre-push"]);
|
||||
expect(fs.readFileSync(path.join(hooks, "pre-push"), "utf8")).toBe(original);
|
||||
const installed = git(repo, "rev-parse", "--path-format=absolute", "--git-path", "hooks/pre-push");
|
||||
expect(fs.readFileSync(installed, "utf8")).toContain("gstack-redact pre-push (managed)");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status, push.stderr).toBe(0);
|
||||
expect(push.stderr).not.toContain("developer-hook");
|
||||
} finally {
|
||||
for (const [key, value] of Object.entries(prev)) {
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("installed pre-push guard uses the actual destination", () => {
|
||||
test("origin tracking at HEAD cannot excuse the same credential on first push to publish", () => {
|
||||
const { repo, publish, head } = fixture();
|
||||
expect(git(repo, "rev-parse", "origin/main")).toBe(head);
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(publish, "for-each-ref", "--format=%(refname)")).toBe("");
|
||||
@@ -71,7 +110,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
test("a configured remote's different push URL cannot reuse its fetch tracking tip", () => {
|
||||
const { repo, publish } = fixture();
|
||||
git(repo, "remote", "set-url", "--push", "origin", publish);
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(publish, "for-each-ref", "--format=%(refname)")).toBe("");
|
||||
@@ -92,7 +131,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
git(repo, "update-ref", "refs/heads/publish/main", tip);
|
||||
expect(git(repo, "rev-parse", "publish/main")).toBe(tip);
|
||||
expect(git(repo, "rev-parse", "refs/remotes/publish/main")).toBe(base);
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(publish, "for-each-ref", "--format=%(refname)")).toBe("");
|
||||
@@ -108,7 +147,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
expect(git(repo, "rev-parse", "refs/remotes/publish/main")).toBe(git(repo, "rev-parse", "HEAD"));
|
||||
git(repo, "remote", "set-url", "--add", "publish", second);
|
||||
expect(git(repo, "remote", "get-url", "publish")).toBe(publish);
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(second, "for-each-ref", "--format=%(refname)")).toBe("");
|
||||
@@ -116,7 +155,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
|
||||
test("direct URL push cannot borrow origin's tracking history", () => {
|
||||
const { repo, publish } = fixture();
|
||||
const push = spawnSync("git", ["push", publish, "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", publish, "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(publish, "for-each-ref", "--format=%(refname)")).toBe("");
|
||||
@@ -125,8 +164,8 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
test("an advertised remote tip absent from local objects cannot justify a guessed base", () => {
|
||||
const { repo, publish } = fixture();
|
||||
const remoteTip = seedIndependentRemote(repo, publish);
|
||||
expect(spawnSync("git", ["cat-file", "-e", remoteTip], { cwd: repo, timeout: 30_000 }).status).not.toBe(0);
|
||||
const push = spawnSync("git", ["push", "--force", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
expect(spawnSync("git", ["cat-file", "-e", remoteTip], { cwd: repo, timeout: 30_000, env: gitEnv() }).status).not.toBe(0);
|
||||
const push = spawnSync("git", ["push", "--force", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(publish, "rev-parse", "refs/heads/main")).toBe(remoteTip);
|
||||
@@ -135,21 +174,21 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
test("an absent advertised tip permits a clean forced update after conservative scanning", () => {
|
||||
const { repo, publish, head } = fixture(false);
|
||||
const remoteTip = seedIndependentRemote(repo, publish);
|
||||
expect(spawnSync("git", ["cat-file", "-e", remoteTip], { cwd: repo, timeout: 30_000 }).status).not.toBe(0);
|
||||
const push = spawnSync("git", ["push", "--force", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
expect(spawnSync("git", ["cat-file", "-e", remoteTip], { cwd: repo, timeout: 30_000, env: gitEnv() }).status).not.toBe(0);
|
||||
const push = spawnSync("git", ["push", "--force", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(git(publish, "rev-parse", "refs/heads/main")).toBe(head);
|
||||
});
|
||||
|
||||
test("new SHA-256 branch scans the correct empty tree and allows a clean push", () => {
|
||||
const { repo, publish } = fixture(false, true);
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "publish", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(git(publish, "rev-parse", "refs/heads/main")).toHaveLength(64);
|
||||
fs.writeFileSync(path.join(repo, "leak.txt"), `key ${key}\n`);
|
||||
git(repo, "add", "leak.txt");
|
||||
git(repo, "commit", "-qm", "new secret");
|
||||
const blocked = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const blocked = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(blocked.status).toBe(1);
|
||||
expect(blocked.stderr).toContain("aws.access_key");
|
||||
});
|
||||
@@ -164,7 +203,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "clean.txt"), "safe update\n");
|
||||
git(repo, "add", "clean.txt");
|
||||
git(repo, "commit", "-qm", "clean branch");
|
||||
const push = spawnSync("git", ["push", "origin", "main", "leak"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main", "leak"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(head);
|
||||
@@ -183,7 +222,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
git(repo, "add", "clean.txt");
|
||||
git(repo, "commit", "-qm", "clean update");
|
||||
const head = git(repo, "rev-parse", "HEAD");
|
||||
const push = spawnSync("git", ["push", "origin", "main", ":disposable"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main", ":disposable"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(head);
|
||||
expect(git(origin, "for-each-ref", "--format=%(refname)", "refs/heads/disposable")).toBe("");
|
||||
@@ -193,7 +232,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
const { repo, origin, head } = fixture(false);
|
||||
const hook = git(repo, "rev-parse", "--git-path", "hooks/pre-push");
|
||||
const result = spawnSync("bash", [hook, "origin", origin], {
|
||||
cwd: repo, input: "refs/heads/main malformed\n", encoding: "utf8", timeout: 30_000,
|
||||
cwd: repo, input: "refs/heads/main malformed\n", encoding: "utf8", timeout: 30_000, env: gitEnv(),
|
||||
});
|
||||
expect(result.status, result.stderr || result.error?.message).toBe(1);
|
||||
expect(result.stderr).toContain("could not parse a pre-push ref line");
|
||||
@@ -208,7 +247,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "near seam");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.secret_key");
|
||||
});
|
||||
@@ -221,7 +260,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "unlabeled seam");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(push.stderr).not.toContain("aws.secret_key");
|
||||
});
|
||||
@@ -232,7 +271,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "one finding at seam");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr.match(/HIGH aws\.access_key/g)).toHaveLength(1);
|
||||
});
|
||||
@@ -247,7 +286,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "supplementary normalization seam");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr.match(/HIGH aws\.access_key/g)).toHaveLength(1);
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(head);
|
||||
@@ -264,7 +303,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "mid-line control");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(push.stderr).not.toContain("MEDIUM finding");
|
||||
});
|
||||
@@ -277,7 +316,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "embedded key control");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(push.stderr).not.toContain("aws.access_key");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).not.toBe(head);
|
||||
@@ -294,7 +333,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "long bearer advisory");
|
||||
const tip = git(repo, "rev-parse", "HEAD");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(push.stderr).toContain("MEDIUM finding");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(tip);
|
||||
@@ -308,7 +347,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "invisible seam");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.secret_key");
|
||||
});
|
||||
@@ -320,7 +359,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "unscannable context");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("engine.input_too_large");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(head);
|
||||
@@ -339,7 +378,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "payload.txt"), payload);
|
||||
git(repo, "add", "payload.txt");
|
||||
git(repo, "commit", "-qm", "normalization seam");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(expected ? 1 : 0);
|
||||
expect(push.stderr.includes("aws.secret_key")).toBe(expected);
|
||||
});
|
||||
@@ -350,19 +389,19 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "large.txt"), "ordinary content\n".repeat(160_000));
|
||||
git(repo, "add", "large.txt");
|
||||
git(repo, "commit", "-qm", "large clean diff");
|
||||
let push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 60_000 });
|
||||
let push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 60_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
fs.writeFileSync(path.join(repo, "long.txt"), "x".repeat(900_000) + "\n");
|
||||
git(repo, "add", "long.txt");
|
||||
git(repo, "commit", "-qm", "long clean line");
|
||||
push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 60_000 });
|
||||
push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 60_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
const priorTip = git(origin, "rev-parse", "refs/heads/main");
|
||||
expect(priorTip).not.toBe(head);
|
||||
fs.writeFileSync(path.join(repo, "too-long.txt"), "x".repeat(1_100_000) + "\n");
|
||||
git(repo, "add", "too-long.txt");
|
||||
git(repo, "commit", "-qm", "over cap line");
|
||||
push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 60_000 });
|
||||
push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 60_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("engine.input_too_large");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(priorTip);
|
||||
@@ -373,7 +412,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
fs.writeFileSync(path.join(repo, "long.txt"), `${"x".repeat(900_000)} key ${key}\n`);
|
||||
git(repo, "add", "long.txt");
|
||||
git(repo, "commit", "-qm", "long line with key");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(1);
|
||||
expect(push.stderr).toContain("aws.access_key");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(head);
|
||||
@@ -385,7 +424,7 @@ describe("installed pre-push guard uses the actual destination", () => {
|
||||
git(repo, "add", "long.txt");
|
||||
git(repo, "commit", "-qm", "neighboring long lines");
|
||||
const tip = git(repo, "rev-parse", "HEAD");
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000 });
|
||||
const push = spawnSync("git", ["push", "origin", "main"], { cwd: repo, encoding: "utf8", timeout: 30_000, env: gitEnv() });
|
||||
expect(push.status).toBe(0);
|
||||
expect(push.stderr).not.toContain("engine.input_too_large");
|
||||
expect(git(origin, "rev-parse", "refs/heads/main")).toBe(tip);
|
||||
|
||||
Reference in new issue
Block a user