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
8 changes: 6 additions & 2 deletions src/review/content-lane/registry-logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,14 @@ export function assessSubnetDocument(
return fail("malformed-json", "Subnet document must be a JSON object.");
}
const doc = document as { netuid?: unknown; surfaces?: unknown };
if (!Number.isInteger(Number(doc.netuid))) {
// Type-first, like the `!Array.isArray(doc.surfaces)` sibling below: validate the RAW value, never Number(raw)
// (#9665). Number() coerces null/""/[]/booleans/hex/whitespace strings into "valid" integers, so a document
// with `netuid: null` was silently accepted as subnet 0 and threaded into assessSurfaceEntry's consistency
// check as if validated. A netuid is a non-negative index, so a negative integer is rejected too.
if (typeof doc.netuid !== "number" || !Number.isInteger(doc.netuid) || doc.netuid < 0) {
return fail("unsupported-shape", "Subnet document netuid must be an integer.");
}
const netuid = Number(doc.netuid); // normalize once; thread the canonical integer to entry + (future) grounding
const netuid = doc.netuid; // already validated as a non-negative integer above; no coercion on this path
if (!Array.isArray(doc.surfaces)) {
return fail("unsupported-shape", "Subnet document must carry a surfaces[] array.");
}
Expand Down
36 changes: 34 additions & 2 deletions test/unit/content-lane-registry-logic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,42 @@ describe("assessSubnetDocument (whole-file gate: root netuid + exactly-one appen
expect(assessSubnetDocument(dirty, { appendedEntry: entry, secretsScan: false }).verdict).toBe("merged");
});

it("threads the normalized root netuid to the entry (string root coerces; conflicting entry netuid rejected)", () => {
expect(assessSubnetDocument({ netuid: "14", surfaces: [entry] }, { appendedEntry: entry }).verdict).toBe("merged");
it("threads the validated integer root netuid to the entry; a conflicting entry netuid is rejected", () => {
// A string root netuid is no longer coerced/accepted (#9665) — that is covered by the reject table below.
// Here the root is a real integer that matches the entry, and a conflicting entry netuid still fails.
expect(assessSubnetDocument(doc, { appendedEntry: { ...entry, netuid: 14 } }).verdict).toBe("merged");
expect(assessSubnetDocument(doc, { appendedEntry: { ...entry, netuid: 99 } }).reason).toBe("unsupported-shape");
});

describe("root netuid must be a real non-negative integer, not a coercible value (#9665)", () => {
// Before #9665 the check was `!Number.isInteger(Number(doc.netuid))`, so Number() smuggled these through:
// Number(null)===0, Number("")===0, Number([])===0, Number(true)===1, Number("0x10")===16, Number(" 7 ")===7.
for (const bad of [null, "", true, [], [5], "7", "0x10", 1.5, -1, undefined] as unknown[]) {
it(`rejects netuid ${JSON.stringify(bad)} with unsupported-shape`, () => {
const r = assessSubnetDocument({ netuid: bad, surfaces: [entry] }, { appendedEntry: entry });
expect(r.reason).toBe("unsupported-shape");
expect(r.summary).toBe("Subnet document netuid must be an integer.");
});
}

for (const good of [0, 64]) {
it(`keeps a valid integer netuid ${good} (not over-tightened)`, () => {
const r = assessSubnetDocument({ netuid: good, surfaces: [{ ...entry, netuid: good }] }, { appendedEntry: { ...entry, netuid: good } });
expect(r.reason).not.toBe("unsupported-shape");
});
}

it("no longer merges a document with netuid null and a surface declaring netuid 0 (end-to-end regression)", () => {
// The exact exploit: a null root coerced to subnet 0, and a surface netuid of 0 matched it, reaching a
// decisive merge on the surface lane. It must now fail on the root shape check instead.
const r = assessSubnetDocument(
{ netuid: null, surfaces: [{ ...entry, netuid: 0 }] },
{ appendedEntry: { ...entry, netuid: 0 } },
);
expect(r.verdict).not.toBe("merged");
expect(r.reason).toBe("unsupported-shape");
});
});
});

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