Skip to content

Deleting a connection leaves its host allowed in egress_destinations #1956

Closed

Description

@MODSetter

The gap

docs/architecture/egress.md lists this under Known gaps:

Deleting a connection leaves its host's grant in egress_destinations, hidden from Settings › Network, so a later connection to the same host is allowed without asking.

Why it matters

Disconnecting a server is how a user withdraws it. Afterwards Settings › Network stops listing that host, because the list is built from the hosts among the stored connections — so as far as the user can see, the permission is gone. The row is still in the table with enabled true, and the next connection to the same host inherits it: added, probed and talking to the endpoint without anyone being asked.

That is the one thing the egress design is meant to make impossible. It is also silent in both directions — the user cannot see the stale grant to revoke it, and they get no dialog when it is spent.

Where

Note that selected_models.connection_id cascades on delete, so the model selections that used the connection are already cleared by the database. The grant is the one thing left behind.

What done looks like

  • Deleting a connection to a host no other connection uses leaves no enabled row for that host, so adding a connection to it again asks.
  • Deleting one of two connections that share a host does not withdraw the host, because the other one is still there and the user still consented to it. This is the case to get right, and the one your test should cover first.
  • A loopback connection deletes cleanly, having had no destination to begin with.
  • Settings › Network shows the same thing before and after, with no orphan row appearing.
  • Delete the Known gaps line from docs/architecture/egress.md in the same PR.

What not to change

host:huggingface.co is BUILT_IN in modules/egress/service.py: it is contacted whether or not any connection points at it, and it is listed with no connection to discover it from. Whatever you write must not be able to drop it — a user who somehow adds a connection pointing at huggingface.co and then deletes it should not silently lose their model-download consent. Guard against BUILT_IN explicitly rather than relying on nobody doing that.

Do not change list_destinations() to solve this by hiding rows. The bug is the stored grant, not the listing; hiding it would leave the same row to be spent by the next connection.

How to verify

surfsense_local/backend/tests/integration/llm/test_egress.py is the file — it already covers a connection being refused before it is probed, a stored connection listed off and refused, and loopback needing no permission. Write the failing test first: allow a host, delete its connection, add a new connection to the same host, and assert it is refused.

cd surfsense_local/backend
uv run pytest -m integration -k egress
uv run pytest -m unit
uv run ruff check .
cd ../.. && python scripts/check_docs.py

Before you start

Read CONTRIBUTING.md, the egress doc and ADR 0017. This is backend-only and needs no migration — the table shape is fine, only what writes to it is wrong. Open your PR against dev, not main. Comment here to claim this.

Activity

  1. added
    bugSomething isn't working
    help wantedExtra attention is needed
    desktopSurfSense desktop app (surfsense_local)
    on Sep 28, 2026
  2. 8rulerstar commented on Sep 29, 2026

    @8rulerstar
    Contributor

    I'd like to take this one. Plan: in delete_connection(), drop the host's grant only when no other stored connection resolves to the same host (via host_destination / list_destinations), skip loopback, and explicitly guard BUILT_IN so huggingface.co can never be dropped. The first test will be the shared-host case, then allow → delete → re-add → refused. I'll remove the Known gaps line in the same PR, against dev.

  3. MODSetter commented on Sep 29, 2026

    @MODSetter
    OwnerAuthor

    Fixed by #2027, fix(local): withdraw a host's egress grant when its last connection is deleted, by @8rulerstar. Merged into dev.

    Closing by hand rather than by keyword: Fixes #1956 only auto-closes on a merge to the default branch, and this repo merges through dev (CONTRIBUTING), so this would have stayed open and claimable while the work was already done. It reaches main with the next release.

    Nothing left to pick up here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdesktopSurfSense desktop app (surfsense_local)good first issueGood for newcomershelp wantedExtra attention is neededsecurity

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions