init: consolidate all ephron.ren PRDs and docs

This commit is contained in:
Ubuntu
2026-05-15 10:39:54 +08:00
parent 9568533314
commit ee8cddf8b8
21 changed files with 6991 additions and 2 deletions

View File

@@ -0,0 +1,156 @@
# PRD: Canvas iframe 嵌入被安全策略阻止
## 问题描述
Canvas 服务的 `/view/{slug}` 页面通过 iframe 嵌入 `/raw/{slug}` 来展示 Canvas 内容。但由于共享安全头中间件设置了以下策略,浏览器会阻止 iframe 加载:
- `X-Frame-Options: DENY` — 禁止被任何页面 iframe 嵌入
- `frame-ancestors 'none'` — CSP 禁止被任何页面嵌入
**结果:** 用户访问 `canvas.ephron.ren/view/hermes-agent-ai`iframe 区域显示空白或"拒绝连接"。
## 影响范围
- 所有 Canvas 页面的预览功能完全失效
- 首页卡片的缩略图预览也无法加载
## 根因分析
`shared/security_headers.py` 中定义了全局安全头策略:
```python
_CSP_POLICY = (
...
"frame-ancestors 'none'; " # 第17行
...
)
# 第39行
response.headers.setdefault("X-Frame-Options", "DENY")
```
这个策略被所有 ephron.ren 服务共享Auth、Blog、Canvas、Prompt但 Canvas 服务的 `/raw/{slug}` 路由需要被同源 iframe 嵌入。
## 修复方案
### 方案A修改 `/raw/{slug}` 路由(推荐)
`canvas/src/routes/pages.py``raw_canvas` 函数中,返回响应时覆盖安全头:
```python
@router.get("/raw/{slug}", response_class=HTMLResponse)
async def raw_canvas(
slug: str,
ephron_auth: str | None = Cookie(default=None),
):
# ... 现有代码 ...
# Build CSP that allows same-origin iframe embedding
raw_csp = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline'; "
"script-src-elem 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
"style-src 'self' 'unsafe-inline'; "
"style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net https://maxcdn.bootstrapcdn.com; "
"img-src 'self' data: https:; "
"font-src 'self' data: https://fonts.gstatic.com https:; "
"connect-src 'self'; "
"frame-ancestors 'self'; " # Allow same-origin embedding
"base-uri 'self'; "
"form-action 'self' https://*.ephron.ren"
)
return Response(
content=canvas.content_html,
media_type="text/html; charset=utf-8",
headers={
"X-Frame-Options": "SAMEORIGIN", # Override DENY for iframe embedding
"Content-Security-Policy": raw_csp,
},
)
```
**优点:**
- 最精准,只影响需要被嵌入的路径
- 不影响其他服务的安全策略
- 改动范围最小
**缺点:**
- 需要在路由层面重复 CSP 策略
### 方案B修改共享中间件添加路径例外
`shared/security_headers.py` 中添加例外路径:
```python_EXEMPT_PATHS = frozenset({
"/raw/", # Canvas raw content needs iframe embedding
})
@app.middleware("http")
async def _security_headers(request: Request, call_next):
response = await call_next(request)
response.headers.setdefault("X-Content-Type-Options", "nosniff")
# Check if path is exempt from DENY policy
path = request.url.path
if any(path.startswith(p) for p in _EXEMPT_PATHS):
response.headers.setdefault("X-Frame-Options", "SAMEORIGIN")
# Use modified CSP with frame-ancestors 'self'
csp = _CSP_POLICY.replace("frame-ancestors 'none'", "frame-ancestors 'self'")
response.headers.setdefault("Content-Security-Policy", csp)
else:
response.headers.setdefault("X-Frame-Options", "DENY")
response.headers.setdefault("Content-Security-Policy", _CSP_POLICY)
# ... 其他代码 ...
```
**优点:**
- 集中管理,易于维护
- 其他服务如果需要 iframe 嵌入也能受益
**缺点:**
- 修改共享代码,影响所有服务
- 需要更仔细的测试
## 推荐方案
**推荐方案A**,原因:
1. 改动范围最小,只修改 Canvas 服务的一个路由
2. 最精准,只影响 `/raw/{slug}` 路径
3. 不影响其他服务的安全策略
4. 风险最低
## 验证方法
修复后验证:
1. 访问 `https://canvas.ephron.ren/view/hermes-agent-ai`
2. 检查 iframe 是否正常加载内容
3. 检查浏览器控制台是否有 CSP 错误
4. 验证其他页面(首页、管理页)是否正常
```bash
# 检查响应头
curl -sI "https://canvas.ephron.ren/raw/hermes-agent-ai" | grep -E "x-frame-options|content-security-policy"
```
期望输出:
```
x-frame-options: SAMEORIGIN
content-security-policy: ... frame-ancestors 'self'; ...
```
## 相关文件
- `shared/security_headers.py` — 共享安全头中间件
- `canvas/src/routes/pages.py` — Canvas 页面路由(`raw_canvas` 函数)
- `canvas/src/main.py` — Canvas 服务入口(安装安全头中间件)
## 标签
- `bug`
- `security`
- `canvas`
- `iframe`
- `csp`