From 7ed6328482b76325f8674b05c537829cc06f3bef Mon Sep 17 00:00:00 2001 From: real-venus Date: Fri, 17 Jul 2026 01:58:20 -0700 Subject: [PATCH] fix(track-record): exempt the GitHub login line from the public-field blocklist scan (#6772) renderTrackRecordSummaryMarkdown scanned the whole rendered block -- including the caller-provided `- GitHub login: ` identity line -- against PUBLIC_FIELD_BLOCKLIST via assertPublicSummaryText, which throws on any match. A genuine GitHub username containing a blocklisted word bounded by hyphens (a legal username character), e.g. "team-wallet", therefore crashed rendering entirely. The blocklist exists to keep COMPUTED private fields (trust score, reward, ranking, wallet/hotkey/coldkey data) off public surfaces. The login is caller-provided identity, not computed private data, and is already markdown-escaped. This scans only the computed body lines, then renders the login line outside the scan -- byte-identical output, fail-closed behavior preserved for a genuinely-injected term in a computed field. Adds a ROOT vitest suite (the render path was previously covered only by the engine's node:test, which is outside the Codecov patch measurement) exercising the regression, the still-fails-closed computed-field case, the disabled-summary early return, and the optional open-ignored / evidence lines. Closes #6772 --- .../src/track-record-summary.ts | 25 +++++++---- ...ack-record-summary-login-blocklist.test.ts | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 test/unit/track-record-summary-login-blocklist.test.ts diff --git a/packages/loopover-engine/src/track-record-summary.ts b/packages/loopover-engine/src/track-record-summary.ts index 9f53710da2..d7829e84a4 100644 --- a/packages/loopover-engine/src/track-record-summary.ts +++ b/packages/loopover-engine/src/track-record-summary.ts @@ -423,10 +423,7 @@ 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}`, @@ -434,15 +431,25 @@ export function renderTrackRecordSummaryMarkdown(summary: TrackRecordSummary): s ]; 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`; } diff --git a/test/unit/track-record-summary-login-blocklist.test.ts b/test/unit/track-record-summary-login-blocklist.test.ts new file mode 100644 index 0000000000..13136b8739 --- /dev/null +++ b/test/unit/track-record-summary-login-blocklist.test.ts @@ -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); + }); +});