devantler-tech / devantler-tech/ksail
fix(webui): unmanaged clusters with URL-unsafe context names (EKS ARNs) 404 from every web request
- Dominant language
- Go
- Stars
- 165
- Forks
- 12
- Avg merge
- 5h 41m
- Merged PRs (30d)
- 337
Description
> 🤖 Generated by the Agentic Engineer
## Evidence
The web UI interpolates a cluster's name straight into single-segment API routes without escaping it. Every cluster-scoped call in `web/ui/src/api.ts` does this — logs (line 250), get/delete (458, 466), resources (531), kubeconfig (552), apply (596), the resource-detail routes (618) and the exec WebSocket (709):
```ts
return request(`/api/v1/clusters/${namespace}/${name}/resources?${params.toString()}`);
```
For a KSail-managed cluster the name is a DNS-1123 label, so this is safe. It stops being safe now that **unmanaged kubeconfig contexts are surfaced under their raw context name**: a context name is arbitrary text, and the common EKS form is an ARN containing a slash — `arn:aws:eks:us-east-1:123456789012:cluster/prod`.
Probed directly against Go's `ServeMux` with the same route pattern the server uses:
| request path | result |
|---|---|
| `.../clusters/default/plain/resources` | `200`, `PathValue("name") == "plain"` |
| `.../clusters/default/arn:...:cluster%2Fprod/resources` | `200`, `PathValue("name") == "arn:...:cluster/prod"` |
| `.../clusters/default/arn:...:cluster/prod/resources` | **`404`** |
So the slash becomes an extra path segment and the route does not match. `?` and `#` in a context name would truncate or fragment the URL the same way.
## Affected audience and impact
Anyone whose kubeconfig holds an unmanaged context with a name that is not URL-safe — most obviously anyone using EKS, where the ARN form is what `aws eks update-kubeconfig` writes by default.
The effect is that the row is visible but inert: the cluster list shows it, and every action on it fails with a 404 that gives no hint why. That is exactly the "advertised but not operable" state that #6116 set out to remove, reappearing one layer up. The backend resolver added there is correct and does resolve these names; the request never reaches it.
## Expected behaviour
An unmanaged cluster whose context name contains URL-significant characters is operable from the web UI exactly like any other — its resources list, logs stream, kubeconfig downloads, and exec connects.
## Proposed direction
Encode the namespace and name at every cluster-scoped call site in `web/ui/src/api.ts` before interpolation. The probe above establishes that this is sufficient on the server side: Go's `ServeMux` matches the percent-encoded form and hands the handler the decoded value, so no backend change is needed and `PathValue` keeps returning the raw context name the resolver expects.
Two details to get right:
- **The WebSocket URL (line 709) needs the same treatment**, and it is easy to miss because it is built as a full `ws(s)://` string rather than through `request()`.
- **Encode once, centrally if possible.** Eight call sites repeating `encodeURIComponent` is a pattern that drifts — a small helper that builds the cluster-scoped path prefix would keep the ninth call site correct by construction.
## Acceptance criteria
- [ ] Every cluster-scoped request in `web/ui/src/api.ts`, including the exec WebSocket, escapes the namespace and cluster name.
- [ ] A test covers a context name containing `/` and asserts the constructed URL, so a new unescaped call site fails the suite.
- [ ] End-to-end: an unmanaged context named with an EKS ARN can be selected in the UI and its resources listed.
- [ ] Names without special characters produce byte-identical URLs to today (no behaviour change for managed clusters).
## Rough size
S. A mechanical frontend change plus the guard test; the server side needs nothing.
Found while reviewing #6893 (the backend half of #6116). Reported by Codex on that PR and verified independently here.
Contributor guide
Research direction
Start in web/ui/src/api.ts, reviewing every cluster-scoped request and the exec WebSocket URL identified in the issue. Trace how namespace and context names are assembled, then add coverage for a name containing `/`; done means all listed requests escape both path segments, EKS ARN contexts work end to end, and ordinary names produce unchanged URLs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100