Skip to content

feat(orb): trust-gate inbound federated bundles per the #6477 key-trust design - #6649

Merged
loopover-orb[bot] merged 2 commits into
JSONbored:mainfrom
luciferlive112116:feat/federated-bundle-import-6480
Jul 16, 2026
Merged

feat(orb): trust-gate inbound federated bundles per the #6477 key-trust design#6649
loopover-orb[bot] merged 2 commits into
JSONbored:mainfrom
luciferlive112116:feat/federated-bundle-import-6480

Conversation

@luciferlive112116

Copy link
Copy Markdown
Contributor

Summary

The receiving side of federated fleet intelligence. pullPeerBundles (#6479) already fetches peer bundles, but nothing consumed its return value — there was no code path that decided whether a bundle could be trusted, which is exactly the gap #6477 had to close first. This adds that gate: a pulled bundle is verified and trust-checked before it may be folded into local calibration or the peer-median benchmark (#6481), and every rejection is logged with the rule that stopped it.

New src/orb/federated-import.ts, plus the one config field the design requires.

This implements #6477's decision — it does not redesign it

That design pass settled the mechanism deliberately, and this PR follows it literally. Its two poisoning-resistance layers, and where each actually lives:

  1. Explicit allowlist — only a peer whose verification key the operator added to federatedIntelligence.peerKeys is ever considered. Enforced here. This is what makes Sybil self-limiting by construction: forging peers requires the receiving operator to add the attacker's keys themselves.
  2. Median, not meanno code here. The aggregation this feeds already medians (src/orb/analytics.ts:92), so the layer holds by construction. Re-implementing it would fork the exact definition Dashboard benchmark view: gate precision vs peer median for federated fleet intelligence #6481's comparison depends on, which federated-bundle.ts:24-27 explicitly warns against.

#6477 rejected building a reputation/decay/scoring system, so there is deliberately no per-peer score, no anomaly heuristic, and no retroactive poisoned-bundle detection: an operator who finds a bad peer removes its key. Those absences are the design, not gaps.

Three decisions worth reviewing

  • Every allowlisted key is tried; the key is not selected by instanceId. The HMAC is detached and carries no key hint. instanceId is attacker-controlled and unauthenticated until a key verifies, so using it to pick a verifier would let a bundle choose who checks it. The loop is timing-safe (timingSafeEqualHex) and deliberately does not early-exit on a match, so total work doesn't reveal which key matched.
  • untrusted_or_tampered is one reason, not two. With a detached HMAC the receiver genuinely cannot distinguish "peer not allowlisted" from "body tampered" — both are just "no allowlisted key reproduces this signature". Splitting them would report a distinction this scheme cannot make.
  • enabled: true alone never imports. Import requires opt-in and a non-empty allowlist (isFederatedImportEnabled), kept separate from the export's isFederatedIntelligenceEnabled. An operator who turns on the export does not silently start ingesting peer data; an empty allowlist rejects everything (fail closed), mirroring MCP_READ_REPO_ALLOWLIST's posture.

Config

One new field, federatedIntelligence.peerKeys (list of 64-hex keys, default []). Validated at parse time to the shape signFederatedBundle's HMAC key actually has, so an operator can only allowlist something that could really verify a bundle. A malformed entry is dropped with a warning rather than throwing, matching every sibling list field — safe in this direction because dropping a key can only ever remove a peer from consideration, never admit an untrusted one. De-dup is case-insensitive, first occurrence winning (normalizeAutoCloseExemptLogins's shape).

No DB/OpenAPI wiring, deliberately — and this is the part worth checking rather than assuming. federatedIntelligence has no DB-backed counterpart (focus-manifest.ts:465-466: "Mirrors upstreamDriftIssues: exactly: no DB-backed counterpart, so the parsed value … IS the effective value"), so the config-as-code parity chain for RepositorySettings fields doesn't apply here. docs:drift-check agrees: 93 FocusManifest fields all documented.

A warning never echoes a rejected key, and the rejection log carries only the reason plus the opaque instance handle — never bundle contents, never key material. Both are pinned by tests.

Validation

  • Patch coverage 100%, measured — not assumed. From the v8 JSON report: federated-import.ts 44/44 statements, 41/41 branches; all 43 changed lines in focus-manifest.ts covered with zero partial branches. Clears the 99% codecov/patch wall on src/** + packages/loopover-engine/src/** with margin.
  • Root vitest — 772 pass across federated-import, focus-manifest, federated-bundle, federated-collector.
  • Engine node --test — 588/588 pass.
  • npm run typecheck0 errors. Two existing fixtures built the config literal without the new field; vitest doesn't typecheck, so they passed at runtime while tsc failed. Both fixed (the export and the transport each note why they don't read peerKeys).
  • docs:drift-check ok · selfhost:config-lint ok · eslint 0 errors/0 warnings on the new files · git diff --check clean · rebased on latest main, no base conflict.

Tests

Both sides of every branch, including the ones easy to miss:

  • Accepted: valid bundle from an allowlisted peer; verifies against any allowlisted key, not just the first; nullable fields genuinely null (an instance under MIN_DECIDED) still accepted.
  • Rejected: invalid signature; authentically-signed bundle from a non-allowlisted peer (the trust-gating rule itself); tampered body; unknown schemaVersion; wrong-typed signed field; opted-out instance; opted-in with an empty allowlist.
  • Signatures are produced by the real canonicalizeFederatedBundleBody, so an export-side canonicalization change breaks these rather than passing against a local re-statement.
  • An extra field a peer appends is proven not to alter the signed bytes (it's outside the canonical key list).
  • Non-hex/truncated/empty signatures reject without throwing.
  • Rejections are logged by default (never silently dropped), never leak a key or bundle contents, and an unreadable handle logs unknown rather than null.

Scope

  • Six files, one coherent change: the import module + its suite, the config field, and the three fixtures/suites it touches. Wanted paths (src/, packages/, test/).
  • federated-bundle.ts (export) and federated-collector.ts (transport) are unmodified — only their test fixtures gained the new field.
  • No secrets; no changelog, site/, CNAME, or lovable changes. The example keys in tests/docs are locally-invented fixtures ("a".repeat(64), all-zeros), not real material.

Safety

  • Fail-safe is structural, not a guard that could be forgotten: this is a pure function the gate never consults — no DB read, no network call, returns a value rather than mutating. There is no path from a rejected or malformed bundle to this instance's own review/merge behavior.
  • Default OFF: absent peerKeys ⇒ empty ⇒ every inbound bundle rejected, so behavior is unchanged for every existing operator.

Closes #6480

…key-trust design (JSONbored#6480)

Add the receiving side of federated fleet intelligence: verify a pulled peer
bundle's HMAC against the operator's explicit peerKeys allowlist before it can be
folded into local calibration or the peer-median benchmark, and log every
rejection with the rule that stopped it.

Implements JSONbored#6477's decision exactly: explicit operator-configured allowlist (no
auto-discovery, no PKI) plus median-not-mean aggregation, which the existing fleet
analytics already provides. No reputation/decay/scoring system, which that design
pass considered and deliberately turned down.

Closes JSONbored#6480
…ONbored#6480)

vitest does not typecheck, so both fixtures passed at runtime while tsc failed on
the new required field. Each notes why its own side never reads peerKeys.

Also cover defaultRejectionLogger's unknown-handle fallback, the last partial
branch in the new module.
@superagent-security

Copy link
Copy Markdown
Contributor

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

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.65%. Comparing base (db8f3ae) to head (5ae824e).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6649   +/-   ##
=======================================
  Coverage   93.64%   93.65%           
=======================================
  Files         680      681    +1     
  Lines       68026    68075   +49     
  Branches    18673    18686   +13     
=======================================
+ Hits        63704    63753   +49     
  Misses       3347     3347           
  Partials      975      975           
Flag Coverage Δ
shard-1 43.97% <5.66%> (+0.03%) ⬆️
shard-2 36.87% <5.66%> (-0.13%) ⬇️
shard-3 32.42% <15.09%> (+<0.01%) ⬆️
shard-4 34.56% <75.47%> (+0.80%) ⬆️
shard-5 30.93% <5.66%> (-0.69%) ⬇️
shard-6 45.69% <30.18%> (-0.10%) ⬇️

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

Files with missing lines Coverage Δ
packages/loopover-engine/src/focus-manifest.ts 99.03% <100.00%> (+0.01%) ⬆️
src/orb/federated-import.ts 100.00% <100.00%> (ø)

@loopover-orb loopover-orb Bot added the gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier. label Jul 16, 2026
@loopover-orb

loopover-orb Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-16 17:24:43 UTC

8 files · 1 AI reviewer · no blockers · readiness 95/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This adds the receiving-side trust gate for federated bundles: an explicit peer-key allowlist config field (`peerKeys`) plus `src/orb/federated-import.ts`, which shape-validates, schema-version-checks, and HMAC-verifies a pulled bundle against every allowlisted key before acceptance, defaulting to fail-closed (opted out or empty allowlist ⇒ reject everything). The verification loop deliberately tries every key without early-exiting to avoid leaking which key matched via timing, and rejection reasons are logged without leaking peer keys or bundle contents — both sound design choices that match the stated #6477 constraints. Config parsing, defaults, and JSON round-tripping for the new `peerKeys` field are consistently threaded through `focus-manifest.ts` and its existing test suite, and the new module has thorough unit coverage of accept/reject paths.

Nits — 5 non-blocking
  • config/examples/loopover.full.yml and .loopover.yml.example ship an example `peerKeys` entry that is 66 zero characters long, not the 64-hex-char format the code enforces (`FEDERATED_PEER_KEY` in focus-manifest.ts) — harmless since it's commented out, but worth fixing so a copy-paste doesn't silently fail validation.
  • The diff builds `importPeerBundles`/`isFederatedImportEnabled` but no hunk here wires them to `pullPeerBundles`'s return value or the calibration/peer-median fold path described in the PR summary — worth confirming that wiring lands in the linked Dashboard benchmark view: gate precision vs peer median for federated fleet intelligence #6481 rather than being an orphaned export.
  • `isBundleBodyShaped` in federated-import.ts validates types/finiteness but not sane ranges (e.g. negative `windowDays` or `decided` would pass) — likely fine given the sender already produces these, but worth a one-line note on why no range check is needed.
  • Consider a short comment on `verifyFederatedBundle` noting the ~O(peerKeys) HMAC cost per bundle is intentional/bounded, since a reviewer skimming the loop might otherwise flag the non-early-exit as a perf smell.
  • Fix the example peer key length in both example YAML files to exactly 64 hex characters to match the documented/enforced format.

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 #6480
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: 159 registered-repo PR(s), 96 merged, 31 issue(s).
Contributor context ✅ Confirmed Gittensor contributor luciferlive112116; Gittensor profile; 159 PR(s), 31 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The PR adds src/orb/federated-import.ts implementing HMAC verification against an operator-configured peerKeys allowlist, applies #6477's stated design (allowlist + reliance on existing median aggregation) rather than inventing a new heuristic, logs every rejection with an operator-visible reason, is opt-in/fail-closed by default, and includes unit tests covering valid acceptance, invalid signatur

Review context
  • Author: luciferlive112116
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 159 PR(s), 31 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Triage stale or unlinked PRs.
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 &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; 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

@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 729254a into JSONbored:main Jul 16, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:feature Gittensor-scored feature linked to a feature issue — scores a 0.25x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement signature-bundle import + trust-gating validation for federated fleet intelligence

1 participant