Skip to content

feat(browser): loopback extension pairing with scoped tokens - #88203

Open
abundantbeing wants to merge 1 commit into
NousResearch:mainfrom
abundantbeing:feat/browser-extension-pairing
Open

feat(browser): loopback extension pairing with scoped tokens#88203
abundantbeing wants to merge 1 commit into
NousResearch:mainfrom
abundantbeing:feat/browser-extension-pairing

Conversation

@abundantbeing

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds the loopback browser-extension pairing flow so the Hermes Browser extension can obtain a scoped bearer token with one approval click — the gateway half of the extension's "Connect to Hermes" flow.

  • gateway/browser_pairing.py (new): BrowserPairingStore — persistent pending pairings + scoped tokens under HERMES_HOME/state/browser_pairing.json, 180s pairing TTL, 365-day token max age, pruning on load/save, atomic file replace.
  • gateway/platforms/api_server.py: five loopback-only endpoints (start, approve, grant, deny, status), scoped-token acceptance in the API-server auth check (default profile only — a named profile can never be reached with a token minted against the listener key), browserPairing capability advertisement, and a Nous Portal-branded approval page whose assets ship under gateway/assets/browser_pairing/ with graceful fallback to fallback fonts/no artwork when missing.
  • tests/gateway/test_browser_pairing.py: 26 tests covering store lifecycle (create/get/grant/deny/expiry/prune/persistence/token validation), the HTTP handlers, loopback enforcement, the scoped-token auth hook, and the #approveButton/#denyButton DOM contract.
  • docs/browser-extension-pairing.md: flow diagram, endpoint table, token semantics, extension contract.

The approval page keeps the stable DOM contract used by the extension's pairing e2e tests (button ids + form POST actions unchanged).

Related Issue

No issue filed — this is the gateway companion to the Hermes Browser extension's existing pairing client (extension repo, separate). It complements the authenticated browser-controller work in #85351 but is independently mergeable.

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • gateway/browser_pairing.py — new BrowserPairingStore module (161 lines)
  • gateway/platforms/api_server.py — +343 lines: pairing routes, handlers, auth hook, capability advertisement, branded page (assets loaded from gateway/assets/browser_pairing/ at request time)
  • gateway/assets/browser_pairing/ — fonts (Sigurd + JetBrains Mono subsets), dithered statue artwork, badge, globe, wordmark (shipped via existing gateway = ["assets/**/*"] package-data)
  • tests/gateway/test_browser_pairing.py — new test file (26 tests)
  • docs/browser-extension-pairing.md — new doc

How to Test

  1. Start the gateway, then: curl -X POST http://127.0.0.1:8642/api/browser-extension/pair/start -H "Content-Type: application/json" -d '{"name":"Hermes Browser Extension"}' → returns pairing_id + approval_url
  2. Open the approval_url in a local browser → branded approve page with #approveButton / #denyButton
  3. Click Approve → GET /api/browser-extension/pair/status/{pairing_id} returns {"status":"approved","token":"..."}
  4. Use the token as Authorization: Bearer <token> against /v1/models on the default profile → 200
  5. Deny path: POST /api/browser-extension/pair/deny/{pairing_id} → status returns 410 pairing_denied
  6. Non-loopback requests to any pairing endpoint → 403

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A (no config keys added)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

The approval page renders pixel-identical to the locally approved design (verified via headless Chrome against a throwaway server serving this branch's code). Assets degrade gracefully when the gateway/assets/browser_pairing/ directory is absent.

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery tool/browser Browser automation (CDP, Playwright) area/auth Authentication, OAuth, credential pools P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data labels Aug 17, 2026
@abundantbeing

Copy link
Copy Markdown
Contributor Author
ScreenShot Tool -20260817120908

@abundantbeing
abundantbeing force-pushed the feat/browser-extension-pairing branch from 145dd23 to f4a88cd Compare August 19, 2026 15:11
@abundantbeing

Copy link
Copy Markdown
Contributor Author

CI note for the maintainers: the failing slice on this head is unrelated to this PR's diff and reproduces on clean main (b5455fdd1):

  • tests/tools/test_image_generation.py::TestFalCatalog::test_upscale_defaults_are_all_off — reproduced locally against the branch's code, which does not touch test_image_generation.py or the FAL catalog; the assertion fails on the xai/grok-imagine-image/v2.0 catalog entry itself.
  • Clean main's own CI also reports a failing Python-tests slice (slice 12/12 on b5455fdd1).
  • Other flakes observed in the same runs: test_transcription_tools.py::TestRunCommandSttIdleTimeout (timing), test_goal_continuation_drain.py (queue key), and a GitHub 429 during checkout.

A CI re-run (maintainer-only; the fork token gets 403 on actions/rerun) should clear it. Happy to force-push a no-op refresh if that's easier.

Add the Hermes Browser extension pairing flow so any local extension can
obtain a scoped bearer token with one approval click:

- gateway/browser_pairing.py: BrowserPairingStore — persistent pending
  pairings + scoped tokens (HERMES_HOME/state/browser_pairing.json),
  180s pairing TTL, 365-day token max age, prune on load/save.
- gateway/platforms/api_server.py: five loopback-only pair endpoints
  (start / approve / grant / deny / status), scoped-token acceptance in
  the API-server auth check for the default profile only, browserPairing
  capability advertisement, and a Nous Portal-branded approval page
  (assets shipped under gateway/assets/browser_pairing/ with graceful
  fallback when missing).
- tests/gateway/test_browser_pairing.py: store lifecycle + HTTP handler
  coverage, including the #approveButton/#denyButton DOM contract.
- docs/browser-extension-pairing.md: flow, endpoints, token semantics,
  and the extension contract.

The approval page keeps the stable DOM contract used by the extension's
pairing e2e tests (button ids + form POST actions).
@abundantbeing
abundantbeing force-pushed the feat/browser-extension-pairing branch from 78040d1 to 8ccf506 Compare August 21, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/gateway Gateway runner, session dispatch, delivery needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/browser Browser automation (CDP, Playwright) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants