-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathCaddyfile
More file actions
108 lines (103 loc) · 7.04 KB
/
Copy pathCaddyfile
File metadata and controls
108 lines (103 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
{$DOMAIN} {
encode gzip
# The embedded-chat bootstrap document is intentionally nested below an
# opaque app frame. That ancestor can satisfy neither X-Frame-Options:
# SAMEORIGIN nor CSP frame-ancestors 'self'. Keep the exception exact and
# retain the rest of the resource/security policy while the blank document
# establishes its server-verified one-use capability.
# /recover* is excluded from the frame-policy replacements below because
# recoveryd sets STRICTER headers (X-Frame-Options: DENY, frame-ancestors
# 'none'); replacing them here would silently weaken the recovery surface
# to SAMEORIGIN/'self'.
@chatEmbed path /shell/embed/chat
@staticEmbed path /app-embeds/by-id/*
# App frames execute compiled code transferred by their exact parent as a
# short-lived blob module. Keep blob: scoped to this already-opaque frame;
# the ordinary shell never needs blob-backed scripts.
@appFrame path /api/apps/*/frame
# Published sites (/sites/<token>/) are public snapshots of the owner's own
# agent-authored artifacts + Web Studio builds. They must run at an opaque
# origin (CSP `sandbox`, no allow-same-origin) so their JS cannot read the
# shell origin's localStorage/JWT. Like /app-embeds and /recover, /sites is
# EXCLUDED from @notFrameableEmbed and given its own X-Frame-Options + CSP
# block below — Caddy does not guarantee that a later `header >Set` on the
# same field wins over an earlier one, so overlapping matchers on the same
# header are unreliable; a disjoint matcher is the only deterministic form.
# Mirrors the app middleware (_PUBLISHED_SITE_CSP), which likewise keeps
# X-Frame-Options SAMEORIGIN while replacing the CSP, so the boundary holds
# identically at both the proxy and the origin.
@publishedSite path /sites/*
@notFrameableEmbed not path /shell/embed/chat /app-embeds/by-id/* /api/apps/*/frame /recover* /sites/*
header {
# Defer replacements until after reverse_proxy copies the backend
# response. Without `>`, the mirrored backend values are appended and
# browsers receive comma-joined duplicates such as
# `X-Content-Type-Options: nosniff, nosniff`.
>X-Content-Type-Options "nosniff"
>Referrer-Policy "strict-origin-when-cross-origin"
>Permissions-Policy "camera=(), geolocation=()"
>Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# script-src notes:
# 'unsafe-inline' — cannot be removed: index.html, app-frame.html,
# standalone.py, and recover_html.py all contain inline <script>
# blocks (error handlers, postMessage init, BIP capture, factory-
# reset localStorage wipe). Replacing them with nonce/hash-based CSP
# requires server-side rendering changes across all four surfaces.
# https://esm.sh — remains for apps that deliberately use an absolute
# dynamic import while online. Supported bare imports are bundled by
# the app compiler and do not depend on this origin.
}
# img-src includes blob: — the shell creates object URLs for upload
# previews (useFileUpload), the image lightbox, and install-sheet icon
# previews; without blob: those render as broken images under an
# enforced policy.
header @notFrameableEmbed >X-Frame-Options "SAMEORIGIN"
header @notFrameableEmbed >Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://cdn.openai.com; connect-src 'self'; img-src 'self' data: blob:; frame-src 'self' {$MOBIUS_SERVICE_GATEWAY_ORIGIN}; frame-ancestors 'self'"
header @appFrame >X-Frame-Options "SAMEORIGIN"
header @appFrame >Content-Security-Policy "sandbox allow-scripts allow-forms allow-popups allow-top-navigation-by-user-activation; default-src 'self'; script-src 'self' 'unsafe-inline' blob: https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://cdn.openai.com; connect-src 'self'; img-src 'self' data: blob:; frame-src 'self' {$MOBIUS_SERVICE_GATEWAY_ORIGIN}; frame-ancestors 'self'"
# The embedded chat mounts the full ChatView (upload previews, image
# lightbox), so its img-src needs blob: for the same object URLs as the
# shell policy above.
header @chatEmbed >Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://esm.sh; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://cdn.openai.com; connect-src 'self'; img-src 'self' data: blob:; frame-src 'self'"
header @staticEmbed >Content-Security-Policy "sandbox allow-scripts allow-forms allow-pointer-lock; default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' blob:; worker-src 'self' blob:"
# /sites/ headers (disjoint matcher — see the @publishedSite note above).
# X-Frame-Options SAMEORIGIN keeps the page top-level-only; the CSP is
# sandbox-only: the opaque origin is the credential boundary, and resource
# loading is left unrestricted because /sites/ also serves multi-file Web
# Studio sites that pull external assets. Keep in sync with
# _PUBLISHED_SITE_CSP in main.py.
header @publishedSite >X-Frame-Options "SAMEORIGIN"
header @publishedSite >Content-Security-Policy "sandbox allow-scripts allow-forms allow-popups; object-src 'none'; base-uri 'none'; frame-ancestors 'self'"
# Recovery floor lives in the separate recoveryd container (its own
# cgroup + pid1) so a broken or crash-looping platform can't take
# recovery down. Route the recovery surface to it BEFORE the catch-all
# proxy below — `handle` is terminal on match, so /recover* never falls
# through to app:8000. recoveryd exposes 8001 on the compose network.
handle /recover* {
reverse_proxy recoveryd:8001
}
reverse_proxy app:8000
}
# Full backend web services cannot descend from the ordinary opaque mini-app
# frame. Owner-trusted services instead share this one gateway origin, which
# isolates them from the Möbius shell while avoiding one public hostname per
# service. The backend registry still decides which /services/<slug> prefixes
# are enabled; every other path on this host fails closed. Services on this
# gateway share an origin with each other and therefore form one trust group.
{$MOBIUS_SERVICE_GATEWAY_ORIGIN} {
@serviceSurface path /services/*
handle @serviceSurface {
encode gzip
header {
>X-Content-Type-Options "nosniff"
>Referrer-Policy "strict-origin-when-cross-origin"
>Permissions-Policy "camera=(), geolocation=()"
?Content-Security-Policy "frame-ancestors 'self' {$FRONTEND_ORIGIN}"
-X-Frame-Options
}
reverse_proxy app:8000
}
handle {
respond "Not found" 404
}
}