mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
feat(redact-prepush): per-address allowlist for pii.email (gstack.redact.allowEmail)
`git config --add gstack.redact.allowEmail <address>` allows that address for MEDIUM pii.email findings only, so a team alias or support address that has to be in the repo stops warning without reaching for --no-verify. It never affects HIGH findings or any other rule, and the pii.email fix line names it. Controls: a different address still reports, and a HIGH secret next to an allowed address still blocks. Plan item: DX-17. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e7a481a28d
commit
d192d696e7
2 files changed
+35
-6
No files matched your search
@@ -21,8 +21,9 @@
|
||||
* - HIGH finding in added lines → print + exit 1 (block), for public AND private.
|
||||
* - MEDIUM → warn (non-blocking), one line per finding naming its rule,
|
||||
* file line and fix, never the matched value. LOW/WARN → silent.
|
||||
* - `pii.email` skips the pusher's own address and addresses already in
|
||||
* the destination's commit metadata (see emailScanOptions).
|
||||
* - `pii.email` skips the pusher's own address, `gstack.redact.allowEmail`
|
||||
* entries, and addresses already in the destination's commit metadata
|
||||
* (see emailScanOptions).
|
||||
* - GSTACK_REDACT_PREPUSH=skip → log + exit 0 (escape valve).
|
||||
*
|
||||
* Installed/uninstalled via `gstack-redact install-prepush-hook` (see the
|
||||
@@ -511,8 +512,9 @@ function configValues(args: string[]): string[] {
|
||||
|
||||
/**
|
||||
* Addresses `pii.email` treats as not a new leak (#3060): the pusher's own
|
||||
* (`git config user.email`) and the author and committer addresses (raw and
|
||||
* mailmapped) the destination will hold after this push. That last set is the history of every pushed tip
|
||||
* (`git config user.email`), the ones listed in `gstack.redact.allowEmail`,
|
||||
* and the author and committer addresses (raw and mailmapped) the destination
|
||||
* will hold after this push. That last set is the history of every pushed tip
|
||||
* (pushed commits become public with the push, third-party authors included),
|
||||
* of each remote tip being replaced, and, for a ref whose remote tip is new or
|
||||
* unknown, the push remote's own tracking refs. A URL or unknown destination
|
||||
@@ -529,6 +531,7 @@ function emailScanOptions(historyRevs: string[], trackingRemote: string | null):
|
||||
limited?: string;
|
||||
} {
|
||||
const selfEmail = configValues(["--get", "user.email"])[0];
|
||||
const allowed = configValues(["--get-all", "gstack.redact.allowEmail"]);
|
||||
const r = spawnSync("git", [
|
||||
"log", "--no-show-signature", "--no-color", `--max-count=${HISTORY_MAX_COMMITS}`, "--format=%ae%n%aE%n%ce%n%cE",
|
||||
...historyRevs, ...(trackingRemote ? [`--remotes=${trackingRemote}/*`] : []), "--",
|
||||
@@ -540,7 +543,7 @@ function emailScanOptions(historyRevs: string[], trackingRemote: string | null):
|
||||
return {
|
||||
options: {
|
||||
...(selfEmail ? { selfEmail } : {}),
|
||||
...(history ? { repoPublicEmails: [...new Set(history)] } : {}),
|
||||
repoPublicEmails: [...new Set([...(history ?? []), ...allowed])],
|
||||
},
|
||||
...(history ? {} : { limited: timedOut ? `reading commit history took over ${HISTORY_TIMEOUT_MS / 1000} s` : "reading commit history failed" }),
|
||||
};
|
||||
@@ -551,7 +554,10 @@ function mediumLine(f: Finding, part: AddedFile): string {
|
||||
const line = part.lines[f.line - 1];
|
||||
const where = `${part.file || "(unknown file)"}${line ? `:${line}` : ""}` +
|
||||
(part.commit ? ` in commit ${part.commit.slice(0, 12)}` : "");
|
||||
return ` MEDIUM ${f.id} ${where} fix: remove the value before it becomes public\n`;
|
||||
const fix = f.id === "pii.email"
|
||||
? "remove the address, or allow it: git config --add gstack.redact.allowEmail <address>"
|
||||
: "remove the value before it becomes public";
|
||||
return ` MEDIUM ${f.id} ${where} fix: ${fix}\n`;
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
@@ -163,6 +163,7 @@ describe("own and already-public addresses are not reported (#3060)", () => {
|
||||
expect(lines).toHaveLength(1);
|
||||
expect(lines[0]).toContain("pii.email");
|
||||
expect(lines[0]).toContain("notes.md:3");
|
||||
expect(lines[0]).toContain("git config --add gstack.redact.allowEmail <address>");
|
||||
expect(stderr).not.toContain("stranger@corp.io");
|
||||
});
|
||||
|
||||
@@ -302,6 +303,28 @@ describe("each input fails on its own (CEO-23, DX-9)", () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe("per-address allowlist (DX-17)", () => {
|
||||
test("an allowed address is not reported; a different address still is", () => {
|
||||
const fx = fixture();
|
||||
git(fx, fx.repo, ["config", "--add", "gstack.redact.allowEmail", "Team@corp.io"]);
|
||||
commitFile(fx, fx.repo, "CONTACT.md", "team@corp.io\nother@corp.io\n");
|
||||
const { code, stderr } = push(fx, ["origin", "main"]);
|
||||
expect(code).toBe(0);
|
||||
const found = mediumLines(stderr);
|
||||
expect(found).toHaveLength(1);
|
||||
expect(found[0]).toContain("CONTACT.md:2");
|
||||
});
|
||||
|
||||
test("a HIGH secret next to an allowed address still blocks", () => {
|
||||
const fx = fixture();
|
||||
git(fx, fx.repo, ["config", "--add", "gstack.redact.allowEmail", "team@corp.io"]);
|
||||
commitFile(fx, fx.repo, "deploy.env", `OWNER=team@corp.io\nkey ${AWS_KEY}\n`);
|
||||
const { code, stderr } = push(fx, ["origin", "main"]);
|
||||
expect(code).toBe(1);
|
||||
expect(stderr).toContain("aws.access_key");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MEDIUM lines point at the real file line (ENG-8)", () => {
|
||||
test("separated hunks in one file report each hunk's own line", () => {
|
||||
const fx = fixture();
|
||||
|
||||
Reference in new issue
Block a user