Commit c23e038
committed
feat(headers): restore REST/SSE redaction and add deep-merge PATCH
User feedback on the previous commit: dropping REST/SSE redaction
unblocked the UI but narrowed the threat model from PR #425 (a local
process that holds the API key but not filesystem access could read
other upstreams' Bearer tokens off the wire). The better trade-off is
to keep both halves of PR #425 intact AND let the UI edit without
round-tripping the redacted sentinel.
Solution: deep-merge PATCH semantics so the client sends only the keys
that changed. Redacted-but-untouched values stay out of the patch
entirely, the backend keeps the real string on disk.
Backend:
- internal/httpapi/server.go::handlePatchServer — when the request
body contains `headers` or `env`, MERGE into the existing stored map
instead of replacing. New `headers_remove` / `env_remove` fields
carry explicit deletes. Sending both is allowed (deletes apply after
upserts).
- internal/httpapi/server.go::AddServerRequest — adds the two
`*_remove` fields with a comment documenting the new semantics. The
add path ignores them.
- internal/httpapi/server.go::handleGetServers — re-enable
`redactServerHeaders` on both code paths.
- internal/runtime/event_bus.go::emitServersChanged — re-enable
redaction on SSE `servers.changed` payloads. SSE rides the same
trust boundary as the REST GET.
- internal/config/config.go::RevealSecretHeaders — restore the
original PR #425 doc (REST + MCP both gated) with a new paragraph
pointing at the deep-merge mechanism that makes the UI work anyway.
Tests:
- internal/httpapi/patch_server_test.go — 4 new tests pinning the
merge semantics:
- TestHandlePatchServer_HeadersDeepMerge — `headers: {X-New: v}`
against existing `{Authorization, X-Trace}` preserves both
original keys and adds X-New.
- TestHandlePatchServer_HeadersRemove — `headers_remove: [...]`
deletes the listed keys.
- TestHandlePatchServer_HeadersSetAndRemove — both fields in one
PATCH; deletes apply after upserts.
- TestHandlePatchServer_EnvDeepMerge — same pattern for env.
- internal/runtime/event_bus_payload_test.go — restored the original
PR #425 assertion (`...RedactsSensitiveHeaders`).
Frontend (Web UI):
- ServerDetail.vue — `patchServerDiff(patch, action)` replaces the old
`patchKVMap`. Each per-row UI action now sends a minimal targeted
patch:
- Edit one row: `{headers: {key: newValue}}`
- Delete one row: `{headers_remove: [key]}`
- Add one row: `{headers: {newKey: newValue}}`
- Convert to secret: `{headers: {key: "${keyring:NAME}"}}`
The redacted sentinel for unchanged keys never round-trips, by
construction.
- KVValueCell.vue — restore the `isBackendRedacted` branch. When the
cell renders `***REDACTED***`, the reveal / Convert-to-secret
buttons disappear (we don't hold the real value) and the cell shows
the sentinel verbatim with a tooltip explaining that editing still
works through the inline edit button.
macOS Swift:
- ServerDetailView.swift::saveEdits — switch from "send the full
parsed map" to "diff against `server.headers` / `server.env` and
send the diff". New private helper `diffKVMap(original:next:)`
returns a `(set, remove)` tuple suitable for the deep-merge PATCH
body. The same invariant holds: leaving a redacted line untouched
in the textarea produces `next[k] == "***REDACTED***" ==
original[k]`, so the key stays out of both sides of the diff and
the backend preserves the real value.
- ServerDetailView.swift::kvRow — restore the
`value != "***REDACTED***"` gate on the Convert-to-secret button
(the sentinel isn't useful as a keyring payload).
- ServerDetailView.swift::editHeaders doc — explain the new flow.
End-to-end verification against the live local instance:
$ curl -s -H "X-API-Key: ..." /api/v1/servers | jq '...synapbus.headers'
→ {"Authorization": "***REDACTED***"} # redacted ✓
$ jq '...synapbus.headers' ~/.mcpproxy/mcp_config.json
→ {"Authorization": "Bearer 1d386..."} # real on disk
$ curl -X PATCH .../servers/synapbus -d '{"headers":{"X-Trace-Test":"merge-works"}}'
$ jq '...synapbus.headers' ~/.mcpproxy/mcp_config.json
→ {"Authorization": "Bearer 1d386...", "X-Trace-Test": "merge-works"}
# real token preserved ✓
$ curl -X PATCH .../servers/synapbus -d '{"headers_remove":["X-Trace-Test"]}'
$ jq '...synapbus.headers' ~/.mcpproxy/mcp_config.json
→ {"Authorization": "Bearer 1d386..."} # X-Trace-Test deleted ✓
PR #425's E2E tests still pass (TestE2E_PatchDeepMergesEnvAndHeaders,
TestE2E_MultipleEnableDisablePreservesConfig — both exercise the MCP
tool which still redacts). PR #425's intent is fully preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 98a7ff1 commit c23e038
10 files changed
Lines changed: 459 additions & 128 deletions
File tree
- frontend/src
- components
- views
- internal
- config
- httpapi
- runtime
- native/macos/MCPProxy/MCPProxy/Views
- oas
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
35 | 41 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
54 | 63 | | |
55 | 64 | | |
56 | 65 | | |
| |||
114 | 123 | | |
115 | 124 | | |
116 | 125 | | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
117 | 135 | | |
118 | 136 | | |
119 | 137 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2366 | 2366 | | |
2367 | 2367 | | |
2368 | 2368 | | |
2369 | | - | |
2370 | | - | |
2371 | | - | |
2372 | | - | |
2373 | | - | |
2374 | | - | |
2375 | | - | |
2376 | | - | |
2377 | | - | |
2378 | | - | |
2379 | | - | |
| 2369 | + | |
| 2370 | + | |
| 2371 | + | |
| 2372 | + | |
| 2373 | + | |
| 2374 | + | |
| 2375 | + | |
2380 | 2376 | | |
2381 | 2377 | | |
2382 | 2378 | | |
2383 | | - | |
2384 | 2379 | | |
2385 | 2380 | | |
2386 | 2381 | | |
| |||
2397 | 2392 | | |
2398 | 2393 | | |
2399 | 2394 | | |
| 2395 | + | |
| 2396 | + | |
| 2397 | + | |
| 2398 | + | |
| 2399 | + | |
| 2400 | + | |
| 2401 | + | |
2400 | 2402 | | |
2401 | | - | |
2402 | | - | |
2403 | | - | |
2404 | | - | |
| 2403 | + | |
2405 | 2404 | | |
2406 | 2405 | | |
2407 | 2406 | | |
2408 | 2407 | | |
2409 | 2408 | | |
2410 | | - | |
2411 | | - | |
2412 | | - | |
2413 | | - | |
| 2409 | + | |
2414 | 2410 | | |
2415 | 2411 | | |
2416 | 2412 | | |
2417 | 2413 | | |
2418 | | - | |
2419 | | - | |
2420 | | - | |
2421 | | - | |
| 2414 | + | |
| 2415 | + | |
| 2416 | + | |
| 2417 | + | |
2422 | 2418 | | |
2423 | 2419 | | |
2424 | 2420 | | |
2425 | 2421 | | |
2426 | 2422 | | |
2427 | | - | |
2428 | | - | |
2429 | | - | |
2430 | | - | |
| 2423 | + | |
| 2424 | + | |
| 2425 | + | |
| 2426 | + | |
2431 | 2427 | | |
2432 | 2428 | | |
2433 | 2429 | | |
| |||
2470 | 2466 | | |
2471 | 2467 | | |
2472 | 2468 | | |
2473 | | - | |
2474 | | - | |
2475 | | - | |
2476 | | - | |
| 2469 | + | |
| 2470 | + | |
| 2471 | + | |
| 2472 | + | |
2477 | 2473 | | |
2478 | 2474 | | |
2479 | 2475 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
167 | | - | |
168 | | - | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
169 | 170 | | |
170 | | - | |
171 | | - | |
172 | | - | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
173 | 174 | | |
174 | | - | |
175 | | - | |
176 | | - | |
177 | | - | |
178 | | - | |
179 | | - | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
180 | 182 | | |
181 | 183 | | |
182 | 184 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
0 commit comments