Skip to content

fix(gateway): do not treat a failed registry load as no HTTP clients (SBS-900) - #775

Merged
tsouth89 merged 9 commits into
mainfrom
fix/sbs-900-insecure-loopback-registry-load
Aug 16, 2026
Merged

fix(gateway): do not treat a failed registry load as no HTTP clients (SBS-900)#775
tsouth89 merged 9 commits into
mainfrom
fix/sbs-900-insecure-loopback-registry-load

Conversation

@tsouth89

@tsouth89 tsouth89 commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Fixes SBS-900.

User-visible result

A user who starts the HTTP gateway with --insecure-loopback after a failed registry load (load_resolved Err), and without TOOLPORT_HTTP_TOKEN / CONDUIT_HTTP_TOKEN, now sees the gateway refuse to bind instead of opening an unauthenticated loopback listener.

A failed load plus an env token still binds (the token is the auth). A successful empty load — including a missing registry file, which is already Ok(default) — plus the flag still opens.

The bug

resolve_http_caller's open branch used reg.http_clients.is_empty(). That is also true when boot load_resolved returns Err and falls back to Registry::default(). So a corrupt or unreadable registry silently satisfied the "no clients configured" precondition for --insecure-loopback.

Nothing reaches a tool today (the fallback has zero servers). The auth posture of an explicitly-insecure mode still should not flip on a read failure.

Fix

Thread registry_loaded (Ok=true, Err=false) into resolve_http_caller, resolve_http_scope, and startup allow_insecure_open / http_bind_is_authorized. The open branch now requires registry_loaded && http_clients.is_empty().

Watcher reload-fail already keeps the previous registry. That path was not changed.

Existing tests that use Registry::default() as a successful empty registry pass registry_loaded=true.

Sweep

rg -n 'http_clients\.is_empty|allow_insecure_open' (excluding docs/audit/):

Site Action
resolve_http_caller open branch Fixed: also requires registry_loaded
http_allows_insecure_open / http_bind_is_authorized Fixed: open bind requires registry_loaded
serve_http !reg.http_clients.is_empty() Left as-is. This is the inverse check: "do registered clients count as auth?" After a failed load the in-memory default is empty, so it does not invent auth. Treating a failed load as "clients exist" would allow a bind with neither a token nor the flag. Combined with the open hatch it used to authorize --insecure-loopback; the hatch is what this PR closes.
serve_http_loop / handle_connection Plumbing only: pass registry_loaded through
Watcher load_from Err Unchanged: keeps the previous registry

Fail-without-fix

Reverted only the two production gates (&& registry_loaded in resolve_http_caller and in http_allows_insecure_open). Signatures stayed so the tests compiled. Then:

cargo test --manifest-path src-tauri/Cargo.toml --no-default-features --bin toolport-gateway -- failed_registry_load_does_not_open_insecure_loopback
running 1 test
test tests::failed_registry_load_does_not_open_insecure_loopback ... FAILED

failures:

---- tests::failed_registry_load_does_not_open_insecure_loopback stdout ----

thread 'tests::failed_registry_load_does_not_open_insecure_loopback' (556449) panicked at src/bin/toolport-gateway.rs:20309:9:
failed load must not authorize the open caller

failures:
    tests::failed_registry_load_does_not_open_insecure_loopback

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 307 filtered out; finished in 0.00s

Restored the gates. The same test, plus successful_empty_registry_load_still_opens_insecure_loopback, then passed.

Tests

In src-tauri/src/bin/toolport-gateway.rs, both citing SBS-900:

  • failed_registry_load_does_not_open_insecure_loopback — failed load must not open; failed load + no env token must not authorize the open bind; failed load + env token still binds
  • successful_empty_registry_load_still_opens_insecure_loopback — successful empty load + flag still opens

CI (this worktree)

Gate Command Result
format npm run format:check pass
lint npm run lint pass (0 errors; 49 pre-existing warnings)
frontend build npm run build pass
frontend tests npm run test 37 files, 382 tests passed
clippy cargo clippy --manifest-path src-tauri/Cargo.toml --no-default-features --lib --bins pass (exit 0; pre-existing warnings only)
rust (headless) cargo test --manifest-path src-tauri/Cargo.toml --no-default-features --lib --bins --tests pass (lib 1006 passed / 1 ignored; gateway bin 308 passed; integration crates all passed)
rust (CI test:rust, default features) npm run test:rust pass (lib 1085 passed / 1 ignored; gateway bin 308 passed; integration crates all passed)

What this makes more likely

An operator who used --insecure-loopback as a recovery hatch after a corrupt registry, with no env token, now gets a refuse-to-bind error instead of an open listener. They must set TOOLPORT_HTTP_TOKEN or repair the registry. That is the intended fail-closed direction.

A later successful watcher reload after a failed boot does not flip registry_loaded to true. Startup policy stays immutable. Env-token and registered-client auth still work on that path if the process bound.

Gaps / not done

  • Did not change the watcher reload-fail path (already keeps the previous registry).
  • Did not run Windows/macOS rust jobs, installer-script, or smoke:headless.
  • Did not run clippy with default desktop features (CI clippy is --no-default-features).
  • Did not touch /home/box/projects/toolport or other worktrees.
  • Did not commit docs/audit/.
  • NOT MERGED. Linear SBS-900 left open.

Note

Fix gateway to reject insecure loopback binding when registry load fails

  • Introduces a LoadSource enum in registry.rs to track whether a registry load was authoritative (File) or a fallback (FirstRun, Backup, Unreadable).
  • Adds registry_trusted: Arc<AtomicBool> to GatewayState and propagates it through watch_registry/watch_tick so auth decisions reflect live reload outcomes.
  • resolve_http_caller, http_bind_is_authorized, and http_allows_insecure_open now require registry_loaded == true before treating an empty http_clients list as "no clients configured" for the --insecure-loopback escape hatch.
  • The live watcher can now close an insecure listener on reload if the newly loaded registry is non-authoritative.
  • Behavioral Change: a failed, unreadable, or backup-recovered registry no longer satisfies the "empty http_clients" condition; previously this could leave an open unauthenticated listener.

🖇️ Linked Issues

Fixes SBS-900 — the gateway incorrectly treated a failed registry load as having no HTTP clients, potentially opening an insecure loopback listener.

Macroscope summarized 320c0a7.

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d9b78eae-be38-41d9-a191-42bace8abe03

📥 Commits

Reviewing files that changed from the base of the PR and between 02a9c79 and 3ca54bd.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !**/*.md
📒 Files selected for processing (1)
  • src-tauri/src/bin/toolport-gateway.rs

Included review availability: 0 reviews are currently available. Based on recent review activity, included reviews refill at 1 per hour.


📝 Walkthrough

Walkthrough

The gateway now preserves registry-load success state. HTTP startup and request authorization use this state to reject insecure loopback access after failed loads while allowing it for successfully loaded empty registries. Tests cover startup, scope, caller, and integration behavior.

Changes

Registry Load Authorization

Layer / File(s) Summary
Registry load state and startup authorization
src-tauri/src/bin/toolport-gateway.rs
Registry loading returns the registry and a success flag. HTTP startup uses the flag for bind and insecure-listener authorization.
HTTP serving and caller resolution
src-tauri/src/bin/toolport-gateway.rs
The success flag flows through HTTP serving, request handling, scope resolution, and caller resolution.
Authorization and integration coverage
src-tauri/src/bin/toolport-gateway.rs
Tests cover failed loads, successful empty registries, registered clients, scope behavior, registry clearing, caller identity, session owners, and integration setup.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 3ca54

The change makes failed registry loads fail closed for unauthenticated insecure-loopback binding while preserving successful empty-load behavior; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant RegistryLoader
  participant HTTPStartup
  participant HTTPServingLoop
  participant CallerResolution
  RegistryLoader->>HTTPStartup: registry and registry_loaded
  HTTPStartup->>HTTPServingLoop: registry_loaded
  HTTPServingLoop->>CallerResolution: registry_loaded and request scope
  CallerResolution->>CallerResolution: authorize insecure fallback
Loading

Possibly related PRs

Suggested reviewers: bharadwajkanneveti, aryansk

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the gateway fix and accurately describes the main change: failed registry loads must not enable insecure loopback access.
Description check ✅ Passed The description directly explains the bug, implementation, user-visible behavior, tests, and validation results for the gateway authorization changes.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sbs-900-insecure-loopback-registry-load

Comment @coderabbitai help to get the list of available commands.

…loopback-registry-load

# Conflicts:
#	CHANGELOG.md
…stener (SBS-900)

The first pass closed the `load_resolved` Err path. `Ok` is not "we read the
registry" either, and the verdict was a boot constant while the registry it
described is live.

- `load_from` now reports a `LoadSource`: the file itself, a genuine first run,
  a recovery from a backup, or a default standing in for contents that could not
  be read. `read_registry_file` used to fold every read failure into "absent", so
  a locked or unreadable registry came back looking like a first run; it now
  separates a not-found from a read that failed. A recovery is state N-1 (save_to
  snapshots the pre-write content), so the save that registered the FIRST http
  client is exactly the one whose backup has none. Only `File` and `FirstRun`
  count as authoritative, which is what the `--insecure-loopback` open branch
  now requires. A missing registry file is still a first run and still opens.
- That verdict is now live. The watcher swaps the registry on any `Ok`, so a
  registry corrupted while the gateway runs could hand the request path an empty
  client list and silently re-open a listener that auth had closed. The watcher
  republishes the verdict with every swap, under the registry lock, and the
  request path reads it while holding that lock instead of using the boot value.
- `serve_http` recovers a poisoned registry lock like every other lock in the
  file. `unwrap_or(false)` read a poisoned lock as "no clients registered",
  which is the input that turns auth off.
- Tests drive the real load path: a corrupt registry recovered from a backup is
  not authoritative, a missing file is, an unreadable file is not (unix), a
  watcher tick on a recovered registry publishes the untrusted verdict, and a
  live listener that loses trust starts rejecting unauthenticated requests.
…loopback-registry-load

# Conflicts:
#	CHANGELOG.md
…try we read

SECURITY.md describes the `--insecure-loopback` precondition as "no token and an
empty `http_clients`". That list is also empty when the registry was recovered
from a backup or could not be read, which no longer opens a listener (SBS-900).
…loopback-registry-load

# Conflicts:
#	CHANGELOG.md
…loopback-registry-load

# Conflicts:
#	CHANGELOG.md
…e SBS-900 merge

The SBS-866 collision test landed on main while SBS-900 was adding the
registry_loaded parameter to resolve_http_caller. Git merged both cleanly
but the call site still passed four arguments. The registry loads fine in
this test, so it passes true; the test covers scope resolution, not a
failed load.
…loopback-registry-load

# Conflicts:
#	CHANGELOG.md
#	src-tauri/src/bin/toolport-gateway.rs
@tsouth89
tsouth89 merged commit 0b89fbd into main Aug 16, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant