From 4faef5dff526ff0d78b65f6a07bd0aee36033edf Mon Sep 17 00:00:00 2001 From: Alex Sehl Date: Tue, 29 Sep 2026 15:25:03 +0500 Subject: [PATCH] fix(commit): reject SHA-256 signatures and non-UTF-8 messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signed-commit check only matched the `gpgsig` header, so commits signed in SHA-256 repositories (`gpgsig-sha256`) were rewritten with a signature that no longer verifies. Commit objects are read as UTF-8, so a message stored in another `encoding` would be corrupted on rewrite and in the PR text. Such commits are now rejected with a clear error instead. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) bstack-id: feee043856314f52a15d170ea27e9381 --- src/commit.ts | 16 +++++++++++++++- test/commit.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/commit.ts b/src/commit.ts index 296086c..a11bdd5 100644 --- a/src/commit.ts +++ b/src/commit.ts @@ -84,12 +84,26 @@ export function parseRawCommit(oid: string, raw: string): Commit { ); } - if (headers.some((line) => line.startsWith("gpgsig "))) { + if ( + headers.some( + (line) => line.startsWith("gpgsig ") || line.startsWith("gpgsig-sha256 "), + ) + ) { throw new Error( `Commit ${oid} is signed. bstack cannot add an identity trailer without replacing its signature`, ); } + const encoding = headers + .find((line) => line.startsWith("encoding ")) + ?.slice("encoding ".length); + + if (encoding && !/^utf-?8$/i.test(encoding)) { + throw new Error( + `Commit ${oid} uses the ${encoding} message encoding; bstack only supports UTF-8 commit messages`, + ); + } + const message = raw.slice(boundary + 2); return { diff --git a/test/commit.test.ts b/test/commit.test.ts index f138c44..f2e85ba 100644 --- a/test/commit.test.ts +++ b/test/commit.test.ts @@ -53,6 +53,42 @@ describe("commit identity", () => { }); }); + test.each([ + ["gpgsig -----BEGIN PGP SIGNATURE-----", "is signed"], + ["gpgsig-sha256 -----BEGIN PGP SIGNATURE-----", "is signed"], + ["encoding ISO-8859-1", "uses the ISO-8859-1 message encoding"], + ])("rejects commits with the %s header", (header, message) => { + const raw = [ + "tree aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "parent bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "author Ada 1 +0000", + "committer Ada 1 +0000", + header, + "", + "Original message", + "", + ].join("\n"); + + expect(() => + parseRawCommit("cccccccccccccccccccccccccccccccccccccccc", raw), + ).toThrow(message); + }); + + test("accepts an explicit UTF-8 encoding header", () => { + const raw = [ + "tree aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "parent bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "encoding UTF-8", + "", + "Original message", + "", + ].join("\n"); + + expect( + parseRawCommit("cccccccccccccccccccccccccccccccccccccccc", raw).message, + ).toBe("Original message\n"); + }); + test("rewrites only the parent and message of a raw commit", () => { const raw = [ "tree aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",