feat(13-09): enforce the /aiui/-scoped CSP sandbox boundary (AIUI-04)

Adds a Content-Security-Policy header to both nginx `location /aiui/`
blocks whose 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. Explicitly rejects the
`sandbox` iframe attribute (allow-scripts + allow-same-origin is the
known escape; dropping allow-same-origin breaks AIUI's storage and
its origin-checked bridge) and records why in both the nginx comment
and a new comment above the Chat.vue iframe. Adds
referrerpolicy="no-referrer" to the iframe so a media URL or page path
never leaks upstream via Referer.

Also adds an explicit `location /aiui/api/openrouter/ { return 404; }`
to both server blocks, closing 13-02's Task 3 checkpoint finding
(operator-accepted deviation 2026-08-03): the relay was already
structurally gone but the SPA catch-all served 200/405 instead of 404.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-04 03:11:36 -04:00
co-authored by Claude Opus 5
parent 5e3aab30a2
commit 6ac0ebbf0a
2 changed files with 54 additions and 1 deletions
+12 -1
View File
@@ -30,7 +30,17 @@
</div>
</Transition>
<!-- AIUI iframe on mobile, leave room for close bar + tab bar at bottom -->
<!-- AIUI iframe on mobile, leave room for close bar + tab bar at bottom.
No `sandbox` attribute: it was considered and rejected for this
phase (AIUI-04, 13-RESEARCH.md Open Question 2). `allow-scripts`
together with `allow-same-origin` is the well-known escape pattern,
and dropping `allow-same-origin` moves AIUI to an opaque origin,
breaking its storage and its origin-checked postMessage bridge a
change bigger than this phase budgeted. The enforced boundary
instead is the /aiui/-scoped Content-Security-Policy (nginx) plus
the node-side rate limit (G-B3, 13-12); the residual risk (a
browser that ignores or partially enforces CSP) is named in
13-AI-SPEC.md §6, not silently assumed away. -->
<iframe
v-if="aiuiUrl"
ref="aiuiFrame"
@@ -38,6 +48,7 @@
:title="t('chat.aiAssistant')"
class="chat-iframe chat-iframe-mobile"
allow="microphone"
referrerpolicy="no-referrer"
style="background: transparent"
/>