fix: stop the app shell answering for /.well-known/
Release / Build and publish image (push) Failing after 9m56s
Release / Build and publish image (push) Failing after 9m56s
Adding the connector to Claude failed with "Couldn't register with Verdande's sign-in service", on a server that has no sign-in service and needs none. Claude probes /.well-known/oauth-authorization-server before connecting to an MCP server. The SPA fallback answered 200 with a page of HTML — which reads as "yes, there is an authorization server here" — so it went on to dynamic client registration and failed there instead of concluding the obvious thing, which is that this server does not use OAuth and the key in the URL is the whole credential. /.well-known/ is reserved by RFC 8615 and is never a route in this app. A 404 is the true answer, and it is also the useful one: the client stops asking and uses the URL as given. This is the third time the catch-all has caused a confusing failure by saying 200 to something that does not exist — /mcp itself was the second. The fallback should hand back the shell for routes the app might have, not for every path on the server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Kristian Wind
co-authored by
Claude Opus 5
parent
7ab68b0b58
commit
3f4af6c0ab
@@ -929,3 +929,57 @@ func TestMailAddressPrefersTheConfiguredSender(t *testing.T) {
|
||||
t.Error("an SMTP host is set, so configured should be true")
|
||||
}
|
||||
}
|
||||
|
||||
// A 200 with the app shell is an answer that says "yes, that exists" to anything
|
||||
// that asks. For /.well-known/ that is actively harmful: it is a reserved
|
||||
// namespace, and a client probing it is asking a yes-or-no question.
|
||||
//
|
||||
// Claude asks /.well-known/oauth-authorization-server before connecting to an
|
||||
// MCP server. It read the shell as "there is an authorization server here" and
|
||||
// then failed trying to register with it, reporting a broken sign-in service on
|
||||
// a server that has none and needs none.
|
||||
func TestWellKnownDoesNotAnswerWithTheAppShell(t *testing.T) {
|
||||
ts := newTestServer(t)
|
||||
ts.bootstrap(t)
|
||||
|
||||
for _, path := range []string{
|
||||
"/.well-known/oauth-authorization-server",
|
||||
"/.well-known/oauth-protected-resource",
|
||||
"/.well-known/openid-configuration",
|
||||
"/.well-known/anything-at-all",
|
||||
} {
|
||||
resp, err := ts.client.Get(ts.URL + path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("%s: status %d, want 404", path, resp.StatusCode)
|
||||
}
|
||||
if strings.Contains(string(body), "<!doctype html") {
|
||||
t.Errorf("%s: answered with the app shell", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The one well-known path that is real still works — it is a route, not a
|
||||
// fallthrough, so the rule above must not have swallowed it.
|
||||
func TestWellKnownCalDAVStillRedirects(t *testing.T) {
|
||||
ts := newTestServer(t)
|
||||
ts.bootstrap(t)
|
||||
|
||||
client := &http.Client{
|
||||
CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
|
||||
}
|
||||
resp, err := client.Get(ts.URL + "/.well-known/caldav")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
t.Error("CalDAV discovery was caught by the well-known rule")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,6 +365,21 @@ func (s *Server) serveWeb(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// /.well-known/ is reserved by RFC 8615 and is never a route in the app.
|
||||
// Handing back the shell there does not merely waste a response: it tells
|
||||
// whoever asked that the thing they were looking for exists.
|
||||
//
|
||||
// That is not hypothetical. Claude probes
|
||||
// /.well-known/oauth-authorization-server before connecting to an MCP server,
|
||||
// read 200 and a page of HTML as "there is an authorization server here", and
|
||||
// then failed trying to register with it — reporting a broken sign-in service
|
||||
// on a server that has none and needs none. A 404 means "no OAuth here", the
|
||||
// client stops asking, and the key in the URL is used as intended.
|
||||
if strings.HasPrefix(r.URL.Path, "/.well-known/") {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
|
||||
return
|
||||
}
|
||||
if s.web == nil {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
Reference in New Issue
Block a user