Skip /js/ in the Access Gate and set denied-page CSP so Denied UI can render.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Konrad Neitzel
2026-08-28 12:39:47 +02:00
co-authored by Cursor
parent 198ef0e204
commit 984b0c2335
7 changed files with 134 additions and 7 deletions
+16 -2
View File
@@ -181,9 +181,10 @@ func (g AccessGate) normalized() *AccessGate {
func (g *AccessGate) writeDenied(w http.ResponseWriter, r *http.Request) {
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').
// 200 plus frame-ancestors 'self': AppAPI's default proxy CSP uses
// frame-ancestors 'none' unless the ExApp sets CSP, which blanks iframes.
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("Content-Security-Policy", deniedCSP)
w.WriteHeader(http.StatusOK)
_, _ = w.Write(deniedHTML)
return
@@ -197,6 +198,9 @@ func acceptsHTML(accept string) bool {
func (g *AccessGate) shouldSkip(path string) bool {
path = normalizeGatePath(path)
if isTopMenuScriptPath(path) {
return true
}
for _, p := range defaultSkipPaths {
if path == p {
return true
@@ -210,6 +214,13 @@ func (g *AccessGate) shouldSkip(path string) bool {
return false
}
// isTopMenuScriptPath reports AppAPI top-menu bootstrap scripts under /js/.
// Those must load for a non-member so the shell can show Denied UI; gating
// them yields a blank embedded page (script Accept is not text/html → 403).
func isTopMenuScriptPath(path string) bool {
return path == "/js" || strings.HasPrefix(path, "/js/")
}
func normalizeGatePath(path string) string {
path = strings.TrimSuffix(path, "/")
if path == "" {
@@ -220,6 +231,9 @@ func normalizeGatePath(path string) string {
var defaultSkipPaths = []string{"/heartbeat", "/enabled", "/init"}
// deniedCSP lets AppAPI proxy the denied page into an ExApp iframe.
const deniedCSP = "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'; style-src 'unsafe-inline'"
var deniedHTML = []byte(`<!DOCTYPE html>
<html lang="en">
<head>