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:
co-authored by
Claude Opus 5
parent
5e3aab30a2
commit
6ac0ebbf0a
@@ -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 —
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user