mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-19 00:00:13 +02:00
dd4dd9e1f5
- /ship: scan the composed PR body + title before create AND edit, from a temp file (exact bytes scanned = bytes sent). HIGH blocks the PR (no skip); MEDIUM confirms per finding. Codex/Greptile/eval sections go in tool-attributed fences so example credentials those tools quote WARN-degrade instead of blocking the PR — a live-format credential inside the fence still blocks. - /document-release: scan the PR-body temp file before gh pr edit. - /document-generate: scan the staged doc diff (added lines) before commit — generated docs often carry example credentials; a live-format secret blocks. Tests: ship-template-redaction (incl. tool-fence WARN-degrade contract), document-skills-redaction. All skills stay under the v1.47 size budget. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.4 KiB
TypeScript
55 lines
2.4 KiB
TypeScript
/**
|
|
* /ship redaction wiring (T5/T11). The PR body + title are scanned at-sink before
|
|
* create AND edit; tool output goes in attributed fences so example credentials
|
|
* WARN-degrade instead of blocking; create/edit file from the scanned temp file.
|
|
*/
|
|
import { describe, test, expect } from "bun:test";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import { scan } from "../lib/redact-engine";
|
|
|
|
const ROOT = path.resolve(import.meta.dir, "..");
|
|
const TMPL = fs.readFileSync(path.join(ROOT, "ship", "SKILL.md.tmpl"), "utf-8");
|
|
|
|
describe("/ship redaction wiring", () => {
|
|
test("scans the PR body via the shared bin before create", () => {
|
|
expect(TMPL).toContain("gstack-redact --from-file");
|
|
expect(TMPL).toMatch(/Redaction scan \(PR body \+ title\)/);
|
|
});
|
|
test("creates from the scanned temp file (exact bytes)", () => {
|
|
expect(TMPL).toMatch(/gh pr create[\s\S]{0,120}--body-file "\$PR_BODY_FILE"/);
|
|
});
|
|
test("edit path also scans before sending", () => {
|
|
expect(TMPL).toMatch(/gh pr edit --body-file "\$PR_BODY_FILE"/);
|
|
expect(TMPL).toMatch(/same redaction scan-at-sink.*before editing/i);
|
|
});
|
|
test("HIGH blocks the PR (exit 3), no skip", () => {
|
|
expect(TMPL).toMatch(/BLOCKED — credential in PR body/);
|
|
});
|
|
test("instructs wrapping tool output in attributed fences (TENSION-3)", () => {
|
|
expect(TMPL).toMatch(/tool-attributed fences/);
|
|
expect(TMPL).toMatch(/codex-review/);
|
|
expect(TMPL).toMatch(/greptile/);
|
|
});
|
|
test("scans the title too", () => {
|
|
expect(TMPL).toMatch(/scan the title/i);
|
|
});
|
|
});
|
|
|
|
describe("tool-attributed fence behavior (engine contract /ship relies on)", () => {
|
|
test("a doc-example credential inside a tool fence WARN-degrades, does not block", () => {
|
|
const body = "## Codex review\n```codex-review\nflagged your_aws_key AKIAIOSFODNN7EXAMPLE\n```";
|
|
const r = scan(body, { repoVisibility: "public" });
|
|
expect(r.counts.HIGH).toBe(0);
|
|
});
|
|
test("a live-format credential inside a tool fence STILL blocks", () => {
|
|
const body = "```codex-review\nleaked AKIA1234567890ABCDEF\n```";
|
|
const r = scan(body, { repoVisibility: "public" });
|
|
expect(r.counts.HIGH).toBe(1);
|
|
});
|
|
test("a credential in plain PR prose (no fence) blocks", () => {
|
|
const body = "We hardcoded AKIA1234567890ABCDEF in the config";
|
|
expect(scan(body, { repoVisibility: "public" }).counts.HIGH).toBe(1);
|
|
});
|
|
});
|