Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions packages/loopover-engine/src/track-record-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,26 +423,33 @@ export function shouldIncludeTrackRecordSummary(
*/
export function renderTrackRecordSummaryMarkdown(summary: TrackRecordSummary): string {
if (!summary.enabled) return "";
const lines = [
"### Public contributor record",
"",
`- GitHub login: ${markdownSafe(summary.login)}`,
const bodyLines = [
`- Resolved public PRs: ${summary.outcomes.resolved} (${summary.outcomes.merged} merged, ${summary.outcomes.closedWithoutMerge} closed without merge)`,
`- Public merge rate: ${summary.mergeRate.label}`,
`- Public tenure: ${summary.tenure.label}`,
`- Public conduct record: ${summary.incidents.label}`,
];

if (summary.outcomes.openIgnored > 0) {
lines.push(`- Open PRs ignored for rate: ${summary.outcomes.openIgnored}`);
bodyLines.push(`- Open PRs ignored for rate: ${summary.outcomes.openIgnored}`);
}
if (summary.incidents.hasPublicIncident && summary.incidents.evidenceUrls.length > 0) {
lines.push(
bodyLines.push(
`- Public evidence: ${summary.incidents.evidenceUrls.map((url) => markdownSafe(url)).join(", ")}`,
);
}

const rendered = `${lines.join("\n")}\n`;
assertPublicSummaryText(rendered);
return rendered;
// #6772: fail-closed on the COMPUTED fields only -- a blocklisted term there would be a genuine leak. The
// GitHub login is caller-provided identity (already markdown-escaped below), not computed private data, so a
// legitimate username that merely contains a blocklisted word bounded by hyphens (e.g. "team-wallet") must
// NOT crash rendering. Scanning the whole block including the identity line was the bug.
assertPublicSummaryText(bodyLines.join("\n"));

const lines = [
"### Public contributor record",
"",
`- GitHub login: ${markdownSafe(summary.login)}`,
...bodyLines,
];
return `${lines.join("\n")}\n`;
}
41 changes: 41 additions & 0 deletions test/unit/track-record-summary-login-blocklist.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { computeTrackRecordSummary, renderTrackRecordSummaryMarkdown } from "../../packages/loopover-engine/src/track-record-summary";

// #6772: `renderTrackRecordSummaryMarkdown` used to scan the ENTIRE rendered block -- including the caller-
// provided `GitHub login:` identity line -- against PUBLIC_FIELD_BLOCKLIST, so a genuine username that merely
// contains a blocklisted word bounded by hyphens (e.g. "team-wallet") crashed rendering. The fix scans only the
// computed fields. This is a ROOT vitest suite (not the engine's node:test one) so the changed lines land in the
// Codecov patch measurement for `packages/loopover-engine/src/**`.
const NOW = "2026-07-04T18:00:00.000Z";
const config = { includeTrackRecordSummary: true, warnings: [] as string[] };

describe("renderTrackRecordSummaryMarkdown login vs public-field blocklist (#6772)", () => {
it("REGRESSION: renders a genuine GitHub login containing a blocklisted word (team-wallet) instead of throwing", () => {
const summary = computeTrackRecordSummary({ login: "team-wallet", now: NOW, config, outcomes: [] });
const md = renderTrackRecordSummaryMarkdown(summary);
expect(md).toContain("- GitHub login: team-wallet");
});

it("still fails closed when a COMPUTED field carries a blocklisted term (the exemption is identity-line-only)", () => {
const summary = computeTrackRecordSummary({ login: "miner", now: NOW, config, outcomes: [] });
expect(() =>
renderTrackRecordSummaryMarkdown({ ...summary, incidents: { ...summary.incidents, label: "trust score leaked" } }),
).toThrow(/blocked public field/u);
});

it("returns an empty string for a disabled summary (no rendering, no scan)", () => {
const base = computeTrackRecordSummary({ login: "miner", now: NOW, config, outcomes: [] });
expect(renderTrackRecordSummaryMarkdown({ ...base, enabled: false })).toBe("");
});

it("renders the optional open-ignored and public-evidence lines when present (both conditional branches)", () => {
const base = computeTrackRecordSummary({ login: "octocat", now: NOW, config, outcomes: [] });
const md = renderTrackRecordSummaryMarkdown({
...base,
outcomes: { ...base.outcomes, openIgnored: 3 },
incidents: { ...base.incidents, hasPublicIncident: true, evidenceUrls: ["https://example.test/record"] },
});
expect(md).toContain("- Open PRs ignored for rate: 3");
expect(md).toMatch(/- Public evidence: .*example\.test\/record/u);
});
});