1 Commits
Author SHA1 Message Date
Konrad NeitzelandCursor 69af4d19c8 Serve Access Gate denied HTML as 200 for iframe display.
AppAPI sets frame-ancestors none on 403 proxy responses, which blanked the ExApp iframe; keep API denials as 403.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 18:43:01 +02:00
2 changed files with 18 additions and 4 deletions
+17 -3
View File
@@ -177,8 +177,10 @@ func (g AccessGate) normalized() *AccessGate {
func (g *AccessGate) writeDenied(w http.ResponseWriter, r *http.Request) { func (g *AccessGate) writeDenied(w http.ResponseWriter, r *http.Request) {
if acceptsHTML(r.Header.Get("Accept")) { if acceptsHTML(r.Header.Get("Accept")) {
// 200 so AppAPI's proxy CSP keeps frame-ancestors 'self' and the ExApp
// iframe can show the message (403 responses get frame-ancestors 'none').
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusOK)
_, _ = w.Write(deniedHTML) _, _ = w.Write(deniedHTML)
return return
} }
@@ -216,7 +218,19 @@ var defaultSkipPaths = []string{"/heartbeat", "/enabled", "/init"}
var deniedHTML = []byte(`<!DOCTYPE html> var deniedHTML = []byte(`<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head><meta charset="utf-8"><title>Access denied</title></head> <head>
<body><h1>Access denied</h1><p>You are not a member of a required group for this app.</p></body> <meta charset="utf-8">
<title>Access denied</title>
<style>
:root { font-family: ui-sans-serif, system-ui, sans-serif; color: #1a1a1a; }
body { margin: 2rem; max-width: 40rem; }
h1 { font-size: 1.4rem; margin-bottom: 0.5rem; }
p { color: #444; line-height: 1.5; }
</style>
</head>
<body>
<h1>Access denied</h1>
<p>You are not a member of a required group for this app. Ask an administrator to add you to the group if you need access.</p>
</body>
</html> </html>
`) `)
+1 -1
View File
@@ -159,7 +159,7 @@ func TestAccessGateDeniesNonMemberWithHTML(t *testing.T) {
req.Header.Set("Accept", "text/html,application/xhtml+xml") req.Header.Set("Accept", "text/html,application/xhtml+xml")
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
h.ServeHTTP(rec, req) h.ServeHTTP(rec, req)
if rec.Code != http.StatusForbidden { if rec.Code != http.StatusOK {
t.Fatalf("got %d", rec.Code) t.Fatalf("got %d", rec.Code)
} }
if !strings.Contains(rec.Header().Get("Content-Type"), "text/html") { if !strings.Contains(rec.Header().Get("Content-Type"), "text/html") {