diff --git a/image-recipe/configs/nginx-archipelago.conf b/image-recipe/configs/nginx-archipelago.conf index 37a98f97..983ca9cd 100644 --- a/image-recipe/configs/nginx-archipelago.conf +++ b/image-recipe/configs/nginx-archipelago.conf @@ -35,9 +35,37 @@ server { } # AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing + # + # /aiui/-scoped CSP (AIUI-04, D-19 unaffected — this is a build-time/ + # runtime property, not a repository-location one): this header governs + # ONLY the document served from this location (it replaces, not adds to, + # the site-wide policy above — nginx add_header does not inherit from + # the previous level once the current level declares its own, same as + # the Cache-Control line below already does at this location). Its + # connect-src is scoped to the AIUI path prefix, so AIUI's own + # JavaScript is browser-prevented from issuing a same-origin fetch to + # /rpc/v1 with the ambient session cookie. This makes AIUI-04's + # "sandboxed by construction" an enforced boundary rather than the + # code-discipline convention the old proxy comment further down + # mistakenly implied. It does NOT split AIUI onto a different origin — + # DOM, storage, and cookies are still shared with the rest of the site; + # only what this policy polices (script/style/connect/etc. sources) is + # restricted. The residual risk (a browser that ignores or partially + # enforces CSP) is named, not silently assumed away, in 13-AI-SPEC.md + # §6 and mitigated by G-B3's rate limit on assistant.chat (13-12). location /aiui/ { try_files $uri $uri/ /aiui/index.html; add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' blob: data:; connect-src $scheme://$host:*/aiui/ blob: data:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always; + } + + # AIUI's own JS reaching a paid/relay path directly (openrouter was + # deleted outright in 13-02 — no proxy_pass to openrouter.ai survives + # anywhere in this config) must not silently 200 via the SPA catch-all + # below. Explicit here rather than bolted onto 13-02 after the fact + # (13-02's Task 3 checkpoint, operator-accepted 2026-08-03). + location /aiui/api/openrouter/ { + return 404; } # AIUI assets fallback — AIUI may reference /assets/ without /aiui/ prefix @@ -956,10 +984,24 @@ server { } # AIUI SPA (Chat mode iframe) — SPA fallback for client-side routing + # + # /aiui/-scoped CSP — see the HTTP server block above for the full + # rationale (AIUI-04, D-19 unaffected). Both server blocks must carry + # this header — a change applied to only one leaves AIUI's JS able to + # reach /rpc/v1 with the ambient session cookie on whichever block + # actually serves the request, same class of gap as T-13-15. location /aiui/ { try_files $uri $uri/ /aiui/index.html; add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; media-src 'self' blob: data:; connect-src $scheme://$host:*/aiui/ blob: data:; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always; } + + # AIUI's own JS reaching a paid/relay path directly must not silently + # 200 via the SPA catch-all below — see the HTTP server block above. + location /aiui/api/openrouter/ { + return 404; + } + # See the HTTP server block above for the full rationale: re-pointed to # the session-gated Rust daemon (T-13-08/T-13-09), OpenRouter relay # deleted outright (T-13-10). Both server blocks must carry this fix — diff --git a/neode-ui/src/views/Chat.vue b/neode-ui/src/views/Chat.vue index e486fa7b..58c72865 100644 --- a/neode-ui/src/views/Chat.vue +++ b/neode-ui/src/views/Chat.vue @@ -30,7 +30,17 @@ - +