Skip to content

Fix: Web UI delete server functionality and add comprehensive tests#97

Merged
Dumbris merged 1 commit into
mainfrom
fix/web-ui-delete-server
Oct 21, 2025
Merged

Fix: Web UI delete server functionality and add comprehensive tests#97
Dumbris merged 1 commit into
mainfrom
fix/web-ui-delete-server

Conversation

@Dumbris

@Dumbris Dumbris commented Oct 21, 2025

Copy link
Copy Markdown
Member

Summary

This PR fixes the HTTP 500 error when deleting servers from the Web UI and adds comprehensive test coverage for the delete functionality.

Problem

When users tried to delete a server using the Web UI delete button, they encountered HTTP 500 errors. The root cause was a mismatch between the frontend API call and the backend MCP handler:

  • Frontend was calling upstream_servers tool with operation: "delete"
  • Backend expects operation: "remove" (defined in internal/server/mcp.go:38)

Changes

Frontend (Web UI)

  1. Fixed API call (frontend/src/services/api.ts)

    • Changed operation from 'delete' to 'remove'
  2. Added alphabetical server sorting (frontend/src/stores/servers.ts)

    • Servers now sorted by name to match tray menu behavior
    • Prevents server list from shuffling on updates
  3. Implemented delete functionality

Backend (Tests)

  1. Unit tests (internal/server/mcp_test.go)

    • TestHandleRemoveUpstream with 6 test cases covering:
      • ✅ Successful removal of existing servers
      • ✅ Error handling for non-existent servers
      • ✅ Security checks for read-only mode
      • ✅ Security checks for disabled management
      • ✅ Permission checks for AllowServerRemove flag
      • ✅ Removal of quarantined servers
  2. E2E test (internal/server/mcp_test.go)

    • TestE2E_DeleteServerFlow with complete deletion flow:
      1. Add a test server
      2. Verify server appears in the list
      3. Delete the server using operation: "remove"
      4. Verify server is removed from the list
      5. Verify deleting non-existent server fails with proper error

Testing

All tests pass successfully:

=== RUN   TestHandleRemoveUpstream
--- PASS: TestHandleRemoveUpstream (0.00s)
    --- PASS: TestHandleRemoveUpstream/successful_removal_of_existing_server
    --- PASS: TestHandleRemoveUpstream/fail_to_remove_non-existent_server
    --- PASS: TestHandleRemoveUpstream/fail_to_remove_in_read-only_mode
    --- PASS: TestHandleRemoveUpstream/fail_to_remove_when_management_disabled
    --- PASS: TestHandleRemoveUpstream/fail_to_remove_when_not_allowed
    --- PASS: TestHandleRemoveUpstream/successfully_remove_quarantined_server

=== RUN   TestE2E_DeleteServerFlow
--- PASS: TestE2E_DeleteServerFlow (12.92s)

Also verified with Playwright browser automation:

  • ✅ Delete button appears on server cards
  • ✅ Modal confirmation dialog displays correctly
  • ✅ Server deletion completes successfully
  • ✅ Toast notification appears
  • ✅ Server removed from list (count decreased from 23 to 22)
  • ✅ No HTTP 500 errors

Screenshots

Before: Clicking delete resulted in HTTP 500 error
After: Delete button works with confirmation modal and success toast

Checklist

  • Bug fix implemented and verified
  • Unit tests added (6 test cases)
  • E2E tests added (complete flow coverage)
  • All tests passing
  • Frontend built successfully (make build)
  • Verified with browser automation
  • Server list now sorted alphabetically
  • Confirmation modal prevents accidental deletions

Related Issues

Fixes the HTTP 500 error reported by user when attempting to delete servers from Web UI.

This commit fixes the HTTP 500 error when deleting servers from the Web UI
and adds comprehensive test coverage.

## Changes

### Frontend (Web UI)
- Fix API call to use correct operation name (`remove` instead of `delete`)
  - `frontend/src/services/api.ts`: Changed operation from 'delete' to 'remove'
- Add alphabetical server sorting to match tray menu behavior
  - `frontend/src/stores/servers.ts`: Sort servers by name using localeCompare
- Implement delete button with confirmation modal
  - `frontend/src/components/ServerCard.vue`: Added Delete button and modal dialog
  - `frontend/src/stores/servers.ts`: Added deleteServer() function with optimistic updates

### Backend (Tests)
- Add unit tests for server deletion validation logic
  - `internal/server/mcp_test.go`: TestHandleRemoveUpstream with 6 test cases
  - Tests cover: success cases, error handling, security checks, permissions
- Add E2E test for complete deletion flow
  - `internal/server/mcp_test.go`: TestE2E_DeleteServerFlow with 5-step verification
  - Tests: add server → verify exists → delete → verify removed → error on re-delete

## Bug Fix
**Root Cause**: Frontend was calling upstream_servers tool with operation="delete"
but the backend MCP handler expects operation="remove" (defined in mcp.go:38 as operationRemove).

**Impact**: All delete operations from Web UI returned HTTP 500 errors.

**Solution**: Updated frontend to use correct operation name "remove".

## Testing
All tests pass:
- ✅ TestHandleRemoveUpstream (6 test cases)
- ✅ TestE2E_DeleteServerFlow (complete deletion flow)
- ✅ Verified with Playwright browser automation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@Dumbris

Dumbris commented Oct 21, 2025

Copy link
Copy Markdown
Member Author

CI Test Results

Summary

All tests related to this PR passed successfully

The CI shows one failing test, but it's unrelated to these changes:

Test Results:

  • TestE2E_DeleteServerFlow - PASSED (13.43s) - This is the new test added in this PR
  • TestHandleRemoveUpstream - PASSED (unit tests for delete functionality)
  • ✅ All unit tests passed across all platforms (macOS, Linux, Windows)
  • ✅ All linting checks passed
  • ✅ Frontend tests passed
  • ✅ Build succeeded on all platforms

Unrelated Failure:

  • TestE2E_QuarantineConfigApply - FAILED on Windows with race condition

This test:

  1. Was not modified by this PR
  2. Failed with race detected during execution of test - a pre-existing race condition
  3. Is a known flaky test (E2E tests show intermittent failures on main branch as well)

Verification:

The logs show my new test completed successfully:

--- PASS: TestE2E_DeleteServerFlow (13.43s)

All changes in this PR (delete functionality, sorting, unit tests, E2E tests) are working correctly.

@Dumbris Dumbris merged commit ca7e44c into main Oct 21, 2025
32 of 34 checks passed
@Dumbris Dumbris deleted the fix/web-ui-delete-server branch October 27, 2025 08:10
Dumbris added a commit that referenced this pull request Jun 22, 2026
…1076) (#745)

* feat(registries): harden registry fetches against SSRF (CWE-918, MCP-1076)

Registry server-list fetches use a user-supplied URL (`registry add-source`),
which CodeQL flags as go/request-forgery (#97/#98): a malicious or typo'd
registry source could point the daemon at internal/metadata endpoints
(169.254.169.254, RFC1918 ranges).

Add an SSRF guard centered on a single `isBlockedIP` predicate (loopback,
RFC1918, RFC6598 CGNAT, link-local incl. the cloud-metadata endpoint,
IPv6 unique-local, unspecified, multicast; fails closed), applied at three
layers:

- dial time: a net.Dialer Control hook on the shared registry HTTP client
  rejects the actual resolved IP before connect — covers hostnames that resolve
  into blocked ranges and closes the DNS-rebinding TOCTOU window a parse-time
  check alone cannot.
- fetch pre-flight: validateRegistryURL rejects a literal-IP host (defense in
  depth + an explicit barrier at the http.NewRequest sink).
- add-source/edit-source: literal-IP fail-fast (no DNS, stays pure/offline) so a
  bad source is refused up front with the stable invalid_registry_url code.

Opt out with a new top-level `allow_private_registry_fetch` config flag
(default false) for operators who run a trusted registry mirror on an
internal/private address.

TDD: table tests for every blocked/allowed range (incl. CGNAT and RFC1918
boundaries), an end-to-end loopback dial block, add-source literal-IP rejection,
and the two registry-add E2E daemon tests opt in via the new flag.

* test(server): opt loopback registry fetch tests past the SSRF guard

The in-process server tests that fetch a registry served on a loopback httptest
server (consistency_official, mcp_add_from_registry's startTestRegistry helper)
now hit the new SSRF dial guard, because the registries-package test bypass does
not apply to the server package's test binary. Set AllowPrivateRegistryFetch on
their SetRegistriesFromConfig configs — exactly how an operator opts in for a
trusted internal mirror — so the fetch is permitted while the production guard
stays active for the add-source rejection test.

Fixes the linux/arm64 Build Binaries leg (TestCrossSurfaceConsistency_*,
TestHandleUpstreamServers_AddFromRegistry_*).

* fix(registries): close SSRF proxy-bypass with app-layer host resolution

CodexReviewer (round 2) found a real gap: the registry client clones
http.DefaultTransport, which keeps Proxy: ProxyFromEnvironment. With
HTTP_PROXY/HTTPS_PROXY set, the dialer connects to the PROXY as the first hop,
so registryDialControl only ever validates the proxy's IP — never the real
target. A hostname resolving to loopback/private/link-local (incl.
169.254.169.254) could still be fetched through a proxy.

Add an application-layer guard (guardRegistryTargetHost) that resolves the
target host and rejects the fetch if ANY resolved IP is in a blocked range. It
runs before the request in registryGet, independent of the transport/proxy, so
it holds in the proxy case; the dial-time Control stays as defense-in-depth for
the direct path and DNS-rebind TOCTOU. Fail-open on resolver error (a flaky
resolver must not break every fetch); literal IPs already covered pre-flight;
relaxed by the allow_private_registry_fetch opt-in.

Regression coverage: a public-looking hostname resolving to 169.254.169.254 is
refused with HTTP(S)_PROXY set; plus direct predicate tests (mixed
public+private resolution blocked, all-public allowed, resolver-error fail-open,
public literal allowed). Resolver is injectable via a package var for
deterministic, network-free tests.
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