fix(dashboard): relax frame security headers when running under launcher#8554
Conversation
When AstrBot is launched by the AstrBot Launcher, the dashboard is embedded in a cross-origin iframe (the Tauri webview). The plugin page responses set X-Frame-Options: SAMEORIGIN and CSP frame-ancestors 'self', both of which inspect the *entire* ancestor chain — not just the immediate parent. Because the top-level Tauri webview has a different origin, these headers block the plugin page from loading inside the nested iframe, resulting in 'localhost refused to connect'. Fix: skip the restrictive frame headers when ASTRBOT_LAUNCHER=1 is set, which the launcher already injects as an environment variable. Other security measures (iframe sandbox, JWT asset_token, postMessage bridge) remain in place.
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Right now the CSP header is completely omitted when
ASTRBOT_LAUNCHERis set, which also drops theobject-src 'none'andbase-uri 'self'protections; consider still sending CSP under the launcher and only relaxing or omitting theframe-ancestorsdirective. - Instead of reading
os.environdirectly in the route helper, it may be cleaner to centralize theASTRBOT_LAUNCHERcheck in a configuration/utility function so that launcher-specific behavior stays consistent and easier to adjust later.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Right now the CSP header is completely omitted when `ASTRBOT_LAUNCHER` is set, which also drops the `object-src 'none'` and `base-uri 'self'` protections; consider still sending CSP under the launcher and only relaxing or omitting the `frame-ancestors` directive.
- Instead of reading `os.environ` directly in the route helper, it may be cleaner to centralize the `ASTRBOT_LAUNCHER` check in a configuration/utility function so that launcher-specific behavior stays consistent and easier to adjust later.
## Individual Comments
### Comment 1
<location path="astrbot/dashboard/routes/plugin.py" line_range="801-797" />
<code_context>
+ # cross-origin iframe (the Tauri webview). Since frame-ancestors and
+ # X-Frame-Options inspect the *entire* ancestor chain, enforcing them here
+ # would block plugin pages from loading inside the nested iframe.
+ if not os.environ.get("ASTRBOT_LAUNCHER"):
+ response.headers["X-Frame-Options"] = "SAMEORIGIN"
+ response.headers["Content-Security-Policy"] = (
+ "frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
+ )
</code_context>
<issue_to_address>
**🚨 suggestion (security):** In launcher mode the CSP is dropped entirely; consider preserving non-framing directives for defense in depth.
When `ASTRBOT_LAUNCHER` is set, we currently remove both `X-Frame-Options` and the entire CSP. While that’s necessary for `frame-ancestors`, it also drops `object-src 'none'` and `base-uri 'self'`, which are still valuable protections. Consider adding an `else` branch that sets a CSP omitting only `frame-ancestors` (e.g. `object-src 'none'; base-uri 'self'`) so launcher mode keeps these defenses while allowing framing.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request conditionally disables X-Frame-Options and frame-ancestors CSP directives when running under the AstrBot Launcher to allow embedding in a cross-origin iframe. The review feedback identifies a security regression where other critical CSP directives (object-src and base-uri) are inadvertently disabled under the launcher, and points out that the environment variable check is too loose. A code suggestion is provided to resolve both issues.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
… launcher Keep object-src 'none' and base-uri 'self' in the CSP header even when ASTRBOT_LAUNCHER is set. Only frame-ancestors and X-Frame-Options need to be relaxed because the Tauri webview is a cross-origin ancestor.
Use explicit value check ('1' / 'true') instead of truthiness for the
ASTRBOT_LAUNCHER env var. Always emit a Content-Security-Policy header
and only conditionally prepend frame-ancestors 'self' — this keeps
object-src 'none' and base-uri 'self' active under the launcher.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- Consider caching the
ASTRBOT_LAUNCHERflag at module import time (e.g.,LAUNCHER_MODE = os.getenv('ASTRBOT_LAUNCHER', '').lower() in ('1', 'true')) instead of readingos.environon every response to avoid repeated env lookups and to centralize the parsing logic. - The CSP construction could be made clearer and less error-prone by building it from a list of directives (conditionally appending
frame-ancestors 'self') and joining them, rather than mutating a string in place.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider caching the `ASTRBOT_LAUNCHER` flag at module import time (e.g., `LAUNCHER_MODE = os.getenv('ASTRBOT_LAUNCHER', '').lower() in ('1', 'true')`) instead of reading `os.environ` on every response to avoid repeated env lookups and to centralize the parsing logic.
- The CSP construction could be made clearer and less error-prone by building it from a list of directives (conditionally appending `frame-ancestors 'self'`) and joining them, rather than mutating a string in place.
## Individual Comments
### Comment 1
<location path="astrbot/dashboard/routes/plugin.py" line_range="802-805" />
<code_context>
+ # X-Frame-Options inspect the *entire* ancestor chain, enforcing them here
+ # would block plugin pages from loading inside the nested iframe.
+ csp = "object-src 'none'; base-uri 'self'"
+ if os.environ.get("ASTRBOT_LAUNCHER") not in ("1", "true"):
+ response.headers["X-Frame-Options"] = "SAMEORIGIN"
+ csp = f"frame-ancestors 'self'; {csp}"
+ response.headers["Content-Security-Policy"] = csp
+
return response
</code_context>
<issue_to_address>
**🚨 question (security):** Revisiting CSP policy when ASTRBOT_LAUNCHER is enabled to ensure framing is only as permissive as needed.
With `ASTRBOT_LAUNCHER` enabled, we drop both `X-Frame-Options` and `frame-ancestors`, so the dashboard can be framed by any origin, exposing it to clickjacking in that mode. If the launcher’s origin (or a stable scheme/host) is known, it’d be safer to restrict `frame-ancestors` to `'self'` plus that origin instead of fully disabling it. Otherwise, consider adding a brief note to security docs or config explaining why this trade-off is acceptable.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
When AstrBot is launched by the AstrBot Launcher, the dashboard is embedded in a cross-origin iframe (the Tauri webview). The plugin page responses set X-Frame-Options: SAMEORIGIN and CSP frame-ancestors 'self', both of which inspect the entire ancestor chain — not just the immediate parent. Because the top-level Tauri webview has a different origin, these headers block the plugin page from loading inside the nested iframe, resulting in 'localhost refused to connect'.
Fix: skip the restrictive frame headers when ASTRBOT_LAUNCHER=1 is set, which the launcher already injects as an environment variable. Other security measures (iframe sandbox, JWT asset_token, postMessage bridge) remain in place.
Modifications / 改动点
Screenshots or Test Results / 运行截图或测试结果
Before the repair:


After repair:
Checklist / 检查清单
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Bug Fixes: