Compare commits

...

1 Commits

Author SHA1 Message Date
diegosouzapw
12a3dbdc76 chore(ci): guard commit identity in pre-commit to stop author misattribution
Two windows of commits in this checkout were signed with the wrong identity,
both caused by an identity override left behind by an automated session:
2026-08-13..26 (name "Xiangzhe" + @backryun's e-mail, 237 commits) and
2026-08-29..09-02 (name "Markus Hartung" + the maintainer's e-mail, 59 commits).
The .mailmap repairs the record after the fact; this gate stops the next window.

The gate is opt-in per machine via omniroute.expectedName / expectedEmail — with
no config it exits 0, so contributors who clone the repo are never affected. It
blocks three things: a committer that is not this machine's identity (which is
what BOTH windows looked like — in August neither the name nor the e-mail was
the maintainer's, so checking only their e-mail would have missed it), an author
carrying the maintainer's e-mail under someone else's name, and any address
listed in omniroute.legacyEmail.

Crediting a contributor with `git commit --author="Name <their@email>"` keeps
working, since the rule targets the committer and the maintainer's own address.
2026-09-05 02:10:30 -03:00
3 changed files with 215 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ fi
# Cheap, deterministic local gates (re-enabled). Slower checks (i18n drift,
# openapi coverage/security-tiers, env-doc sync) run in CI to keep commits fast.
sh scripts/check/check-git-identity.sh
npx lint-staged
node scripts/check/check-docs-sync.mjs
npm run check:any-budget:t11

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env sh
# Guard de identidade de commit — previne misattribution de autoria.
#
# Contexto (ver .mailmap na raiz): este checkout já produziu DUAS janelas de
# commits com autoria trocada, ambas por um override de identidade deixado para
# trás por uma sessão automatizada:
# 1. 2026-08-13..26 — nome "Xiangzhe" + e-mail de @backryun (237 commits)
# 2. 2026-08-29..09-02 — nome "Markus Hartung" + e-mail do mantenedor (59 commits)
#
# Este gate NÃO impõe uma identidade única: contribuidores commitam normalmente
# com a sua, e creditar um contribuidor via `--author` continua funcionando.
# Ele bloqueia apenas as duas assinaturas do defeito:
# (a) um COMMITTER que não é a identidade desta máquina (pega ambas as janelas);
# (b) um AUTHOR com o e-mail do mantenedor sob o nome de outra pessoa;
# (c) um e-mail explicitamente aposentado (`omniroute.legacyEmail`).
#
# Ativação — opcional e por máquina; sem ela o gate é inerte:
# git config --global omniroute.expectedName "diegosouzapw"
# git config --global omniroute.expectedEmail "8016841+diegosouzapw@users.noreply.github.com"
# git config --global --add omniroute.legacyEmail "diegosouzapw@users.noreply.github.com"
expected_name=$(git config --get omniroute.expectedName 2>/dev/null)
expected_email=$(git config --get omniroute.expectedEmail 2>/dev/null)
legacy_emails=$(git config --get-all omniroute.legacyEmail 2>/dev/null)
# Sem configuração nesta máquina o gate não opina — contribuidores não são afetados.
[ -z "$expected_email" ] && exit 0
an=$(git var GIT_AUTHOR_IDENT 2>/dev/null | sed 's/ <.*//')
ae=$(git var GIT_AUTHOR_IDENT 2>/dev/null | sed 's/.*<//; s/>.*//')
cn=$(git var GIT_COMMITTER_IDENT 2>/dev/null | sed 's/ <.*//')
ce=$(git var GIT_COMMITTER_IDENT 2>/dev/null | sed 's/.*<//; s/>.*//')
fail=0
# (a) O COMMITTER é quem executa o commit — nesta máquina, sempre o dono dela.
# Um override de identidade esquecido por uma sessão aparece exatamente aqui,
# e foi o que passou despercebido nas duas janelas: em agosto NEM o nome NEM
# o e-mail eram do mantenedor, então checar só o e-mail dele não bastaria.
if [ "$ce" != "$expected_email" ] || { [ -n "$expected_name" ] && [ "$cn" != "$expected_name" ]; }; then
echo "🛑 COMMITTER não é a identidade desta máquina: $cn <$ce>" >&2
fail=1
fi
# (b) O AUTHOR pode ser um contribuidor (crédito via --author), mas nunca pode
# carregar o e-mail do mantenedor sob o nome de outra pessoa.
if [ -n "$expected_name" ] && [ "$ae" = "$expected_email" ] && [ "$an" != "$expected_name" ]; then
echo "🛑 AUTHOR combina o e-mail do mantenedor com outro nome: $an <$ae>" >&2
fail=1
fi
# (c) e-mails aposentados que já causaram misattribution.
for legacy in $legacy_emails; do
if [ "$ae" = "$legacy" ]; then
echo "🛑 AUTHOR usa e-mail aposentado: $an <$ae>" >&2
fail=1
fi
if [ "$ce" = "$legacy" ]; then
echo "🛑 COMMITTER usa e-mail aposentado: $cn <$ce>" >&2
fail=1
fi
done
[ "$fail" = "0" ] && exit 0
cat >&2 <<MSG
Identidade esperada nesta máquina: $expected_name <$expected_email>
Corrija com:
git config --global user.name "$expected_name"
git config --global user.email "$expected_email"
Para creditar um contribuidor, use o E-MAIL DELE (nunca o seu):
git commit --author="Nome <email-do-contribuidor>"
MSG
exit 1

View File

@@ -0,0 +1,139 @@
// Guards the commit-identity gate (scripts/check/check-git-identity.sh): a stale
// identity override left behind by an automated session must not be able to sign
// commits with the maintainer's e-mail under someone else's name.
//
// Two real incidents motivate this (see .mailmap at the repo root):
// 1. 2026-08-13..26 — name "Xiangzhe" + @backryun's e-mail (237 commits)
// 2. 2026-08-29..09-02 — name "Markus Hartung" + the maintainer's e-mail (59 commits)
import { test } from "node:test";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const SCRIPT_PATH = fileURLToPath(
new URL("../../scripts/check/check-git-identity.sh", import.meta.url)
);
const OWNER_NAME = "diegosouzapw";
const OWNER_EMAIL = "8016841+diegosouzapw@users.noreply.github.com";
const LEGACY_EMAIL = "diegosouzapw@users.noreply.github.com";
/** Runs the gate with a synthetic git identity. `configured` toggles the opt-in. */
function runGate(
identity: {
authorName: string;
authorEmail: string;
committerName: string;
committerEmail: string;
},
configured = true
) {
const env: Record<string, string> = {
...process.env,
GIT_AUTHOR_NAME: identity.authorName,
GIT_AUTHOR_EMAIL: identity.authorEmail,
GIT_COMMITTER_NAME: identity.committerName,
GIT_COMMITTER_EMAIL: identity.committerEmail,
};
if (configured) {
// GIT_CONFIG_* is inherited by every child `git` call the script makes,
// unlike `git -c`, which would only apply to a single invocation.
Object.assign(env, {
GIT_CONFIG_COUNT: "3",
GIT_CONFIG_KEY_0: "omniroute.expectedName",
GIT_CONFIG_VALUE_0: OWNER_NAME,
GIT_CONFIG_KEY_1: "omniroute.expectedEmail",
GIT_CONFIG_VALUE_1: OWNER_EMAIL,
GIT_CONFIG_KEY_2: "omniroute.legacyEmail",
GIT_CONFIG_VALUE_2: LEGACY_EMAIL,
});
}
const r = spawnSync("sh", [SCRIPT_PATH], { env, encoding: "utf8" });
return { status: r.status, stderr: r.stderr ?? "" };
}
const owner = {
authorName: OWNER_NAME,
authorEmail: OWNER_EMAIL,
committerName: OWNER_NAME,
committerEmail: OWNER_EMAIL,
};
test("stays inert when the machine has not opted in", () => {
// A contributor who cloned the repo must never be blocked by the maintainer's gate.
const r = runGate(
{
authorName: "Some Contributor",
authorEmail: "someone@example.com",
committerName: "Some Contributor",
committerEmail: "someone@example.com",
},
false
);
assert.equal(r.status, 0);
});
test("accepts the maintainer's own identity", () => {
assert.equal(runGate(owner).status, 0);
});
test("rejects the maintainer's e-mail carrying another person's name", () => {
const r = runGate({
authorName: "Markus Hartung",
authorEmail: OWNER_EMAIL,
committerName: "Markus Hartung",
committerEmail: OWNER_EMAIL,
});
assert.equal(r.status, 1);
assert.match(r.stderr, /AUTHOR combina o e-mail do mantenedor/);
assert.match(r.stderr, /COMMITTER não é a identidade desta máquina/);
});
test("rejects the retired legacy e-mail — the 2026-08-29 window's exact signature", () => {
const r = runGate({
authorName: "Markus Hartung",
authorEmail: LEGACY_EMAIL,
committerName: "Markus Hartung",
committerEmail: LEGACY_EMAIL,
});
assert.equal(r.status, 1);
assert.match(r.stderr, /e-mail aposentado/);
});
test("allows crediting a contributor through their OWN e-mail", () => {
// `git commit --author="Name <their@email>"` is the sanctioned credit path and
// must keep working — the gate targets the maintainer's e-mail, not the name.
const r = runGate({
authorName: "Markus Hartung",
authorEmail: "mail@hartmark.se",
committerName: OWNER_NAME,
committerEmail: OWNER_EMAIL,
});
assert.equal(r.status, 0);
});
test("rejects a committer that is not this machine's identity", () => {
// The committer is whoever RAN the commit, so on the maintainer's machine it is
// always them. A forgotten identity override surfaces here first.
const r = runGate({
authorName: OWNER_NAME,
authorEmail: OWNER_EMAIL,
committerName: "Bob.Hou",
committerEmail: "houminxi@gmail.com",
});
assert.equal(r.status, 1);
assert.match(r.stderr, /COMMITTER não é a identidade desta máquina/);
});
test("rejects the 2026-08-13 window: neither name nor e-mail is the maintainer's", () => {
// Name "Xiangzhe" (@xz-dev) + @backryun's e-mail. Checking only the maintainer's
// e-mail would MISS this window entirely — hence the committer-identity rule.
const r = runGate({
authorName: "Xiangzhe",
authorEmail: "bakryun0718@proton.me",
committerName: "Xiangzhe",
committerEmail: "bakryun0718@proton.me",
});
assert.equal(r.status, 1);
assert.match(r.stderr, /COMMITTER não é a identidade desta máquina/);
});