Skip to content
Closed
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
11 changes: 8 additions & 3 deletions src/signals/extension-contributor-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ContributorOpportunity, PublicReadinessScore } from "./engine";
import { PUBLIC_UNSAFE_TERMS } from "./redaction";

// ─── Contributor-context payloads for the browser extension (#556) ───────────────────────────────
// The contributor (miner) side of the extension overlay. Every payload here is PUBLIC-SAFE and self-
Expand All @@ -20,9 +21,13 @@ export function contributorReadinessBand(total: number): ContributorReadinessBan
// Defense-in-depth public-safe redaction for any free-form text that reaches the contributor overlay.
// The upstream builders are already contributor-facing, but every string is re-checked here and any
// forbidden private term (reward/wallet/key material/raw trust score/etc.) is redacted rather than
// leaked. Kept local (no import) so this module stays cycle-free and the API never 500s on a stray term.
const FORBIDDEN_EXTENSION_TERMS =
/\b(?:rewards?|payouts?|farming|wallets?|hotkeys?|coldkeys?|seed[-\s]?phrases?|mnemonics?|private[-\s]?keys?|raw[-\s]?trust(?:[-\s]?scores?)?|trust[-\s]?scores?|score[-\s]?(?:estimate|preview|prediction)s?|estimated[-\s]?scores?|scoreability|private[-\s]?reviewability|reviewability[-\s]?internals?|private[-\s]?rankings?)\b/gi;
// leaked. Composed from the canonical `PUBLIC_UNSAFE_TERMS` alternation (redaction.ts) plus a couple of
// extension-only terms that aren't part of that shared vocabulary, so this list can't silently drift
// from the canonical public/private boundary again (#5840). Only the plain string constant is imported
// (not the whole redaction module), and redaction.ts has no imports of its own, so this stays cycle-free.
const FORBIDDEN_EXTENSION_ONLY_TERMS = String.raw`seed[-\s]?phrases?|private[-\s]?keys?`;

const FORBIDDEN_EXTENSION_TERMS = new RegExp(String.raw`\b(?:${PUBLIC_UNSAFE_TERMS}|${FORBIDDEN_EXTENSION_ONLY_TERMS})\b`, "gi");

export function redactExtensionText(text: string): string {
return text.replace(FORBIDDEN_EXTENSION_TERMS, "[redacted]").replace(/\s+/g, " ").trim();
Expand Down
50 changes: 49 additions & 1 deletion test/unit/extension-contributor-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
contributorReadinessBand,
redactExtensionText,
} from "../../src/signals/extension-contributor-context";
import { isPublicSafeText } from "../../src/signals/redaction";
import type { ContributorOpportunity, PublicReadinessScore } from "../../src/signals/engine";

const FORBIDDEN_PUBLIC_TERMS = /wallet|hotkey|coldkey|mnemonic|reward|payout|farming|raw trust|trust score|scoreability|reviewability internals|private ranking/i;
const FORBIDDEN_PUBLIC_TERMS =
/wallet|hotkey|coldkey|mnemonic|reward|payout|farming|raw trust|trust score|scoreability|reviewability|cohort|ranking|miner-originated|human-originated/i;

function opportunity(over: Partial<ContributorOpportunity> = {}): ContributorOpportunity {
return {
Expand Down Expand Up @@ -59,6 +61,52 @@ describe("redactExtensionText", () => {
it("leaves safe text untouched", () => {
expect(redactExtensionText("Maintainer-created issue, good fit.")).toBe("Maintainer-created issue, good fit.");
});

it("redacts a bare cohort reference (#5840)", () => {
expect(redactExtensionText("Cohort diagnostics flagged this PR")).toBe("[redacted] diagnostics flagged this PR");
});

it("redacts a bare ranking reference (#5840)", () => {
expect(redactExtensionText("Your ranking dropped this week")).toBe("Your [redacted] dropped this week");
});

it("redacts miner-originated and human-originated (#5840)", () => {
expect(redactExtensionText("This looks miner-originated, not human-originated")).toBe(
"This looks [redacted], not [redacted]",
);
});

it("redacts a standalone reviewability reference, not just the compound phrases (#5840)", () => {
expect(redactExtensionText("Reviewability is limited right now")).toBe("[redacted] is limited right now");
});
});

describe("FORBIDDEN_EXTENSION_TERMS drift guard (#5840)", () => {
it("redacts a representative sample of every canonical PUBLIC_UNSAFE_TERMS entry, so this module can't silently drift from the shared public/private boundary again", () => {
const samples = [
"Your reward is pending.",
"Your score is pending.",
"Check your wallet balance.",
"Rotate your hotkey.",
"Rotate your coldkey.",
"Keep the mnemonic safe.",
"The payout is scheduled.",
"Your ranking dropped this week.",
"Cohort diagnostics flagged this PR.",
"This looks miner-originated, not human-originated.",
"We detected farming behavior.",
"This uses raw trust internally.",
"The trust score moved.",
"This is private reviewability data.",
"Reviewability is limited right now.",
];

for (const sample of samples) {
// Sanity check: confirm the sample really is flagged unsafe by the canonical pattern.
expect(isPublicSafeText(sample)).toBe(false);
expect(isPublicSafeText(redactExtensionText(sample))).toBe(true);
}
});
});

describe("buildExtensionIssueFit", () => {
Expand Down
Loading