Files
flvx/docs/superpowers/plans/2026-04-26-allow-local-remote-addr.md
T
2026-04-26 16:30:05 +08:00

5.3 KiB

Allow Local Remote Address Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a global settings toggle that allows non-admin forward rules to target local/private addresses when explicitly enabled.

Architecture: Keep the existing remote-address safety validator as the default path for non-admin rule changes, but gate its use behind a single backend config lookup in forward create/update handlers. Surface the toggle through the existing vite_config settings page and prove behavior with backend contract tests first.

Tech Stack: Go net/http + GORM backend, React + TypeScript frontend settings page, Go contract tests.


Task 1: Backend Contract Coverage

Files:

  • Modify: go-backend/tests/contract/forward_contract_test.go

  • Step 1: Write the failing tests

Add contract tests that prove the desired behavior:

t.Run("local remote address is rejected when toggle is off", func(t *testing.T) {
	createPayload := map[string]interface{}{
		"name":       "deny-local-remote",
		"tunnelId":   tunnelID,
		"remoteAddr": "127.0.0.1:8080",
		"strategy":   "fifo",
	}
	createBody, _ := json.Marshal(createPayload)
	createReq := httptest.NewRequest(http.MethodPost, "/api/v1/forward/create", bytes.NewReader(createBody))
	createReq.Header.Set("Authorization", adminToken)
	createReq.Header.Set("Content-Type", "application/json")
	createRes := httptest.NewRecorder()
	router.ServeHTTP(createRes, createReq)

	var out response.R
	_ = json.NewDecoder(createRes.Body).Decode(&out)
	if out.Code == 0 {
		t.Fatalf("expected local remote address to be rejected when toggle is off")
	}
})

t.Run("local remote address is allowed when toggle is on", func(t *testing.T) {
	if err := repo.DB().Exec(`
		INSERT INTO vite_config(name, value, time)
		VALUES(?, ?, ?)
		ON CONFLICT(name) DO UPDATE SET value = excluded.value, time = excluded.time
	`, "allow_local_remote_addr", "1", time.Now().UnixMilli()).Error; err != nil {
		t.Fatalf("enable allow_local_remote_addr: %v", err)
	}

	createPayload := map[string]interface{}{
		"name":       "allow-local-remote",
		"tunnelId":   tunnelID,
		"remoteAddr": "127.0.0.1:8080",
		"strategy":   "fifo",
	}
	createBody, _ := json.Marshal(createPayload)
	createReq := httptest.NewRequest(http.MethodPost, "/api/v1/forward/create", bytes.NewReader(createBody))
	createReq.Header.Set("Authorization", adminToken)
	createReq.Header.Set("Content-Type", "application/json")
	createRes := httptest.NewRecorder()
	router.ServeHTTP(createRes, createReq)
	assertCode(t, createRes, 0)
})
  • Step 2: Run tests to verify they fail

Run: go test ./tests/contract/... -run 'TestForwardContracts|local remote address' Expected: FAIL because backend still rejects local/private addresses unconditionally.

  • Step 3: Commit

Do not commit yet; combine with Task 2 after implementation passes.

Task 2: Backend Toggle Implementation

Files:

  • Modify: go-backend/internal/http/handler/mutations.go

  • Step 1: Add a tiny config helper

Add a helper near other handler helpers:

func (h *Handler) allowLocalRemoteAddr() bool {
	if h == nil || h.repo == nil {
		return false
	}
	cfg, err := h.repo.GetConfigByName("allow_local_remote_addr")
	if err != nil || cfg == nil {
		return false
	}
	return strings.TrimSpace(cfg.Value) == "1"
}
  • Step 2: Gate create/update validation behind the helper

Replace the unconditional checks with:

if !h.allowLocalRemoteAddr() {
	if err := IsSafeRemoteAddr(remoteAddr); err != nil {
		response.WriteJSON(w, response.Err(403, err.Error()))
		return
	}
}
  • Step 3: Run contract tests to verify they pass

Run: go test ./tests/contract/... -run 'TestForwardContracts|local remote address' Expected: PASS

  • Step 4: Run full backend tests

Run: go test ./... Expected: PASS

Task 3: Settings Page Toggle

Files:

  • Modify: vite-frontend/src/pages/config.tsx

  • Step 1: Add the config item to the settings schema

Add a switch-style item for allow_local_remote_addr with warning copy about reduced safety.

  • Step 2: Ensure the key is included in config loading/saving paths

Add allow_local_remote_addr anywhere the page enumerates config keys or groups persisted config values.

  • Step 3: Run frontend build

Run: pnpm run build Expected: PASS

  • Step 4: Run frontend lint

Run: pnpm run lint Expected: 0 errors; existing warnings may remain.

Task 4: Final Verification

Files:

  • Verify only

  • Step 1: Re-run backend contracts for the toggle

Run: go test ./tests/contract/... -run 'TestForwardContracts|local remote address' Expected: PASS

  • Step 2: Re-run full backend tests

Run: go test ./... Expected: PASS

  • Step 3: Re-run frontend build/lint

Run: pnpm run build && pnpm run lint Expected: Build passes, lint has no errors.

  • Step 4: Commit
git add go-backend/internal/http/handler/mutations.go go-backend/tests/contract/forward_contract_test.go vite-frontend/src/pages/config.tsx docs/superpowers/specs/2026-04-26-allow-local-remote-addr-design.md docs/superpowers/plans/2026-04-26-allow-local-remote-addr.md
git commit -m "feat: add allow-local-remote-address toggle"