feat(audit): successful HTTP pushes finally write an audit row #5556
1 changed file+31−0
Modifiedsrc/routes/git.ts+31−0View fileUnifiedSplit
@@ -308,6 +308,37 @@ git.post("/:owner/:repo.git/git-receive-pack", async (c) => {
308308 onPostReceive(owner, repo, refs, pusherUserId).catch((err) =>
309309 console.error("[post-receive] hook error:", err)
310310 );
311
312 // Audit the push itself. Until 2026-08-27 a SUCCESSFUL HTTP push — the
313 // dominant transport — left no audit row at all: only rejections
314 // (push.rejected above) and SSH attempts (git.push.ssh) were recorded,
315 // so "who pushed what, when" was unanswerable from the audit log on
316 // every Gluecron instance. Surfaced by the Vapron deployment, whose
317 // canonical repo had "no push logging". Recorded as the authorized
318 // attempt (post access-check, post policy-gate), mirroring the SSH
319 // path; an in-band pack failure after this point is rare and visible
320 // in the git response itself. Fire-and-forget — never blocks the push.
321 audit({
322 userId: pusherUserId || null,
323 repositoryId: cachedRepoId || undefined,
324 action: "git.push.http",
325 targetType: "repository",
326 targetId: cachedRepoId || undefined,
327 ip: clientIpFrom(c) ?? undefined,
328 userAgent: c.req.header("user-agent") || undefined,
329 metadata: {
330 refs: refs.map((r) => ({
331 ref: r.refName,
332 old: r.oldSha,
333 new: r.newSha,
334 })),
335 },
336 }).catch((err) => {
337 console.warn(
338 `[git] git.push.http audit write failed for ${owner}/${repo}:`,
339 err instanceof Error ? err.message : err
340 );
341 });
311342 }
312343
313344 return response;
314345
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts