Skip to content

fix(content-lane,federated,apr): close the seven pre-flag defects — scrub bypass, probe OOM, self-corroboration, identity farming, peer takeover, tenant binding (#9490) - #9535

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
fix/content-lane-federated-apr
Jul 28, 2026

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Seven defects across the content-lane surface-verification feature, the federated peer-intelligence import, and the APR transfer path. Every one is latent behind a flag that currently ships disabled — which is exactly why they are fixed now, before a flag flip converts them into incidents.

Closes #9490

Content lane (LOOPOVER_REVIEW_SURFACE_VERIFICATION, ships "false")

1. Remote-server text reached the public comment pre-marked as safe. probeFunctionalSurface interpolated the raw content-type header — from the submitter's own server — into a detail string that flows into a finding the wire marks alreadyPublicSafe: true, the flag that skips sanitizeForCheckRun and the forbidden-terms scrub. The wire's invariant ("summary comes solely from a fixed assessment vocabulary") was simply false after #9255. Only a strictly mime-shaped token (type/subtype, ≤64 chars/side) is now ever echoed; anything else renders as unrecognized. Allowlist-from-the-start, not a scrub — nothing hostile survives beyond word chars, dots, pluses, dashes.

2. MAX_PROBE_BODY_CHARS did not bound memory. (await response.text()).slice(0, 64000) buffered the full hostile body before slicing — a streamed multi-hundred-MB response OOMs the isolate before the fail-closed path can run. Now a streaming bounded reader (grounding-wire's house pattern) that cancels at the cap; the regression test serves an infinite stream and pins both the cancel and the chunk count.

3. Unbounded probe fan-out. Each verification fetches up to 2 URLs × 5 hops × 10s, and metagraphed's spec sets maxAppendedEntries: Infinity — a 500-entry PR was a request-amplification primitive and a subrequest-exhaustion path converting into bulk probe_fetch_failed holds for everyone in the isolate. MAX_VERIFIED_ENTRIES_PER_RUN = 10 (matching source-evidence's own cap); entries past it hold for a human, never merge unprobed — the cap bounds spend, it must not become a way to sneak entry #11 through. Counted synchronously before any await, so the concurrent map cannot race the budget; statically-closed entries don't consume it, so padding with invalid entries can't starve real ones.

4. Netuid grounding was self-corroborating. With MIN_STRONG = 1, a submitter's own page asserting "netuid 64" passed grounding — owner and host require an independent source, netuid didn't. It now holds to the strictest standard of the three: the mention must come from the independent source's text. It is the one claim whose self-corroboration was worth farming — the netuid is the subnet identity. Two pre-existing tests pinned the old semantics ("netuid path is source-independent", "grounds from the TARGET page too"); both are rewritten as regressions of the exploit shape, with invariants that source-corroborated netuids still pass and the wss-entry flow is unchanged.

5. Key-order-sensitive entry identity. Both structural diffs keyed entries on raw JSON.stringify, so reordering an existing entry's keys read as a fresh append while removing its prior self from the duplicate check's scope — a farmable path to content-free auto-merged registry PRs. Identity is now canonical (recursive key sort; arrays keep order — order is meaning there, same rule as canonicalJson).

Federated peer intelligence

6. instanceId takeover across allowlisted keys. In-batch dedup and the watermark entry's keyFingerprint both resolved last-wins by instanceId regardless of signing key — so a hostile-but-allowlisted peer B could claim honest peer A's id, ratchet its watermark so A's genuine bundles reject as replayed_or_rollback forever (targeted suppression + stat replacement), and bypass B's own Sybil cap, since only new ids count against it. Squarely inside #9148's declared threat model. An id is now bound to the first key that verified it; a bundle for that id under any other key rejects with a new instance_key_conflict reason. The decisive invariant test: after a takeover attempt, the honest peer's next genuine bundle still lands — neither its watermark nor its binding moved.

7. Unbounded instanceId length silently killed watermark persistence. Nothing bounded the id, and ~3–4 bundles of ~600 KB ids push the single system_flags peer-state blob past D1's ~2 MB value limit — after which writeFederatedPeerState swallowed the failure forever, silently regressing every #9148 protection for every peer. Ids are now capped at 128 chars (empty rejected too) in the shape check, and the write failure logs at error level (structured, forwarder-visible) while still failing open — the sync tick is never failed by it.

APR transfer

Tenant binding, installed now while the route is inert. installationId, repoFullName and newOwner are all caller-supplied, and the only gate was ideaComplete — hard-false today, but the module instructs implementers to "replace the body (keep the signature)". The moment a completion record lands, any authorized caller could transfer any completed APR repo — including another customer's — to themselves. requestAprRepoTransfer now requires a server-side AprRepoBinding (own module, apr-repo-binding.ts, mirroring apr-idea-completion.ts down to the mockable import seam and the fail-closed replace-the-body contract): no binding ⇒ reject; newOwner ≠ bound customer ⇒ reject; installation mismatch ⇒ reject — all before GitHub is ever contacted.

The 404 probe no longer fabricates success. probeAprRepoTransfer treated a bare 404 (repo deleted, App uninstalled) as accepted_departed — a terminal success in the transfer ledger. Departure is now declared only once the repo demonstrably resolves under the target owner; otherwise the transfer stays pending and the existing expiry clock gives the bounded, truthful answer.

Validation

  • npx tsc --noEmit, db:migrations:check, selfhost:env-reference:check, git diff --check — all clean; no schema, env, or OpenAPI changes
  • 522 passed across the eight directly-affected suites, plus 331 across the blast-radius suites (netuid-verification, deliverable-run, flag, scope, source-evidence, spec-resolver, security-scan, safe-url, federated collector/bundle/benchmark, APR creation)
  • Patch coverage against this diff: 0 uncovered changed lines
  • 17 of the new tests verified to fail against the unfixed code (all four touched source files reverted to their pre-fix state and re-run)

Verified solid and deliberately untouched (from the issue's own audit): redirect-hop SSRF re-checks, the three-state fail-closed verdict ordering, safe-url IP-literal coverage, allowlist fail-closed, timing-safe key matching, the collector's bounded reader, MAX_PULLED_BUNDLES, and median-not-mean.

…c-safe scrub bypass, probe OOM/fan-out, self-corroborating grounding, identity farming, peer takeover, and APR tenant binding (#9490)
@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-28 07:04:33 UTC

12 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
The AI review returned non-blocking notes for this change but did not include a separate narrative summary. Review the nits below before deciding this PR.

Nits — 5 non-blocking
  • src/review/content-lane/registry-logic.ts: `echoedCt` is computed unconditionally in `probeFunctionalSurface` even for the `openapi`/default branches that never use it — cheap but unnecessary work on every call.
  • Several new numeric literals (128-char instanceId cap, 10-entry verification/Sybil caps, 64-char mime-token bound) are inline constants without a shared named-constant module; each is individually commented but scattered across files.
  • This single PR touches five production modules across three unrelated subsystems (APR transfer, content-lane, federated-import) — large surface for one cold review pass, even though every piece maps to the same tracked issue content-lane + federated + APR: seven pre-flag defects, incl. remote text bypassing the public-safe scrub and peer instanceId takeover #9490.
  • One of the hostile-header test fixtures in content-lane-registry-logic.test.ts arrived to this review already redacted (`[external-instruction-redacted]`) by an upstream layer, consistent with it resembling injected instruction text used as a demonstration payload for the exact vulnerability being fixed — worth a quick human glance at the actual PR source to confirm it's inert test data, not something that also needs sanitizing elsewhere.
  • Consider centralizing the new magic numbers (MAX_INSTANCE_ID_CHARS, MAX_VERIFIED_ENTRIES_PER_RUN, the 64-char mime bound) with a one-line rationale comment each, as already done, but grouped so a future auditor can find all the content-lane/federated hardening caps in one place.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9490
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 328 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 328 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The diff addresses all seven numbered defects: content-type echo is now allowlisted (#1), probe body reading is bounded via a streaming reader (#2), a per-run verification cap of 10 with holds for excess entries is added (#3), netuid grounding now requires an independent source (#4), entry identity is canonicalized via sorted-key JSON (#5), instanceId is bound to its first verifying key with a rej

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 14 PR(s), 328 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@JSONbored JSONbored self-assigned this Jul 28, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@github-actions

Copy link
Copy Markdown
Contributor

Logic backtest

Replayed 0 historical case(s) for linked_issue_scope_mismatch through the base (6bb8588) and head (b2afbdb) versions of its detection logic (corpus checksum 4f53cda18c2b).

Backtest comparison: linked_issue_scope_mismatch

Verdict: unchanged — no comparable axis moved.

Advisory only — this check never blocks merge (#8105).

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.67%. Comparing base (6bb8588) to head (b2afbdb).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9535      +/-   ##
==========================================
- Coverage   89.56%   88.67%   -0.90%     
==========================================
  Files         843      844       +1     
  Lines      110213   110261      +48     
  Branches    26233    26250      +17     
==========================================
- Hits        98717    97775     -942     
- Misses      10233    11514    +1281     
+ Partials     1263      972     -291     
Flag Coverage Δ
backend 93.65% <100.00%> (-1.63%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/orb/apr-repo-binding.ts 100.00% <100.00%> (ø)
src/orb/apr-repo-transfer.ts 100.00% <100.00%> (ø)
src/orb/federated-import.ts 98.16% <100.00%> (+1.04%) ⬆️
src/review/content-lane/orchestrator.ts 100.00% <100.00%> (ø)
src/review/content-lane/registry-logic.ts 100.00% <100.00%> (ø)
src/review/content-lane/surface-verification.ts 99.12% <100.00%> (+0.13%) ⬆️

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 28, 2026

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit b28db1b into main Jul 28, 2026
9 checks passed
@loopover-orb
loopover-orb Bot deleted the fix/content-lane-federated-apr branch July 28, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

content-lane + federated + APR: seven pre-flag defects, incl. remote text bypassing the public-safe scrub and peer instanceId takeover

1 participant