Commit 8da5d7e
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 20fe758 commit 8da5d7e
10 files changed
Lines changed: 464 additions & 131 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 | |
|---|---|---|---|
| |||
2010 | 2010 | | |
2011 | 2011 | | |
2012 | 2012 | | |
2013 | | - | |
2014 | | - | |
2015 | | - | |
2016 | | - | |
2017 | | - | |
2018 | | - | |
2019 | | - | |
2020 | | - | |
2021 | | - | |
2022 | | - | |
2023 | | - | |
| 2013 | + | |
| 2014 | + | |
| 2015 | + | |
| 2016 | + | |
| 2017 | + | |
| 2018 | + | |
| 2019 | + | |
2024 | 2020 | | |
2025 | 2021 | | |
2026 | 2022 | | |
2027 | | - | |
2028 | 2023 | | |
2029 | 2024 | | |
2030 | 2025 | | |
| |||
2041 | 2036 | | |
2042 | 2037 | | |
2043 | 2038 | | |
| 2039 | + | |
| 2040 | + | |
| 2041 | + | |
| 2042 | + | |
| 2043 | + | |
| 2044 | + | |
| 2045 | + | |
2044 | 2046 | | |
2045 | | - | |
2046 | | - | |
2047 | | - | |
2048 | | - | |
| 2047 | + | |
2049 | 2048 | | |
2050 | 2049 | | |
2051 | 2050 | | |
2052 | 2051 | | |
2053 | 2052 | | |
2054 | | - | |
2055 | | - | |
2056 | | - | |
2057 | | - | |
| 2053 | + | |
2058 | 2054 | | |
2059 | 2055 | | |
2060 | 2056 | | |
2061 | 2057 | | |
2062 | | - | |
2063 | | - | |
2064 | | - | |
2065 | | - | |
| 2058 | + | |
| 2059 | + | |
| 2060 | + | |
| 2061 | + | |
2066 | 2062 | | |
2067 | 2063 | | |
2068 | 2064 | | |
2069 | 2065 | | |
2070 | 2066 | | |
2071 | | - | |
2072 | | - | |
2073 | | - | |
2074 | | - | |
| 2067 | + | |
| 2068 | + | |
| 2069 | + | |
| 2070 | + | |
2075 | 2071 | | |
2076 | 2072 | | |
2077 | 2073 | | |
| |||
2114 | 2110 | | |
2115 | 2111 | | |
2116 | 2112 | | |
2117 | | - | |
2118 | | - | |
2119 | | - | |
2120 | | - | |
| 2113 | + | |
| 2114 | + | |
| 2115 | + | |
| 2116 | + | |
2121 | 2117 | | |
2122 | 2118 | | |
2123 | 2119 | | |
| |||
| 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