diff --git a/README.md b/README.md index a96c718..fe8e5a4 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,11 @@ src/ ## 部署 -- **GitHub Pages**:push `master` 自动触发 [deploy.yml](./.github/workflows/deploy.yml)(typecheck/test/lint/build 全绿后部署;复制 `404.html` 使子路由直链与刷新可用)。 -- **Vercel**:仓库含 [vercel.json](./vercel.json)(含 SPA rewrite),导入仓库即可;锁文件以 `--frozen-lockfile` 安装。 -- **Docker**:见 [Dockerfile](./Dockerfile) 与 [nginx/](./nginx/)(多阶段构建 + nginx 1.30,`docker build` 时通过 `--build-arg CESIUM_ION_TOKEN=` 传参)。产物文件名带内容哈希,仅哈希文件一年长缓存,入口与 Cesium 等无哈希资源每次协商;默认附带基础安全响应头,CSP 需按实际资源来源收敛后在 `nginx/security-headers.conf` 中启用。 +三条产线的安全响应头基线一致(审计 2026-09-22):`X-Content-Type-Options` / `Referrer-Policy` / `X-Frame-Options` / `Permissions-Policy` / CSP;CSP 来源清单按产物逐域核实(高德、Cesium ion、OSM 瓦片、appList 图标、演示视频 CDN),`'unsafe-eval'` 因 Cesium Knockout 模板编译保留。启用 Clarity 统计的部署需自行在 CSP 中追加 `https://www.clarity.ms` 与 `https://c.bing.com`。 + +- **GitHub Pages**:push `master` 自动触发 [deploy.yml](./.github/workflows/deploy.yml)(typecheck/test/lint/build 全绿、产物秘钥扫描通过后部署;复制 `404.html` 使子路由直链与刷新可用)。平台无法自定义响应头,安全头经产物内的 CSP `` 提供(`config/config.github.ts`);`X-Frame-Options` 等响应头级的点击劫持防护为平台限制,介意者请用另外两条产线。 +- **Vercel**:仓库含 [vercel.json](./vercel.json)(含 SPA rewrite 与安全响应头 `headers`),导入仓库即可;锁文件以 `--frozen-lockfile` 安装。 +- **Docker**:见 [Dockerfile](./Dockerfile) 与 [nginx/](./nginx/)(多阶段构建 + nginx 1.30,`docker build` 时通过 `--build-arg CESIUM_ION_TOKEN=` 传参)。产物文件名带内容哈希,仅哈希文件一年长缓存,入口与 Cesium 等无哈希资源每次协商;CSP 以 `Content-Security-Policy-Report-Only` 观察一个迭代周期后在 `nginx/security-headers.conf` 中转正式。 ## 第三方许可说明 diff --git a/config/config.github.ts b/config/config.github.ts index 4ba21d7..d1b5c26 100644 --- a/config/config.github.ts +++ b/config/config.github.ts @@ -17,6 +17,24 @@ export default defineConfig({ PUBLIC_PATH: '/umi-react-admin/', UMI_APP_API_BASE: process.env.UMI_APP_API_BASE, }, + // umi 对 metas 数组为整体覆盖(不与 config.ts 逐项合并),viewport 需一并带上。 + // meta CSP(审计 2026-09-22 H-3):GitHub Pages 无法自定义响应头,只能走 meta; + // meta 版不支持 frame-ancestors(点击劫持防护为平台限制,README 已声明)与 report-only。 + // 来源清单与 nginx/security-headers.conf、vercel.json 保持一致(后者含 frame-ancestors)。 + metas: [ + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { + 'http-equiv': 'Content-Security-Policy', + content: + "default-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; " + + "script-src 'self' 'unsafe-eval' https://*.amap.com; style-src 'self' 'unsafe-inline'; " + + "img-src 'self' data: blob: https://gw.alipayobjects.com https://img.alicdn.com https://*.amap.com https://*.autonavi.com " + + 'https://tile.openstreetmap.org https://ion.cesium.com https://api.cesium.com https://assets.ion.cesium.com https://*.virtualearth.net; ' + + "connect-src 'self' blob: https://*.amap.com https://*.autonavi.com https://ion.cesium.com https://api.cesium.com https://assets.ion.cesium.com https://*.virtualearth.net; " + + "font-src 'self' data:; media-src 'self' https://sf1-cdn-tos.huoshanstatic.com; " + + "worker-src 'self' blob:; frame-src 'self'", + }, + ], favicons: ['/umi-react-admin/favicon.ico'], base: '/umi-react-admin/', publicPath: '/umi-react-admin/', diff --git a/nginx/security-headers.conf b/nginx/security-headers.conf index 24959ad..4ae53f7 100644 --- a/nginx/security-headers.conf +++ b/nginx/security-headers.conf @@ -4,7 +4,20 @@ add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header X-Frame-Options "SAMEORIGIN" always; +add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), fullscreen=(self)" always; -# CSP 未默认开启:地图(Cesium ion、高德)、第三方 CDN 媒体、Web Worker 等来源较多, -# 贸然开启容易大面积白屏。上线前按实际用到的来源收敛后再启用,例如: -# add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: blob: https:; connect-src 'self' https:; worker-src 'self' blob:" always; +# CSP(审计 2026-09-22 H-3):先以 Report-Only 观察一个迭代周期,确认无地图/富文本/播放器 +# 白屏后再去掉 -Report-Only 后缀转正式。来源清单按产物逐域核实(与 vercel.json / config.github.ts 保持一致): +# - 'unsafe-eval':Cesium Knockout 模板编译用 new Function(实测在用),pdfjs 已关 isEvalSupported +# - style 'unsafe-inline':AntD cssinjs 运行时注入 + tailwind 任意值 +# - 高德/autonavi:react-amap JSONP;openstreetmap:InfoMap OSM 瓦片;ion.cesium.com:默认影像 +# - virtualearth.net:未配置 ion token 时 Cesium 的 Bing 影像兜底(元数据 + 瓦片)。 +# 注意瓦片协议跟随页面协议(uriScheme):本站以 http 提供时 Cesium 会请求 http://ecn.t*.tiles…, +# 与 https:// 白名单不匹配(生产 https 部署不受影响);配置了 ion token 的部署走 ion 影像,与此无关 +# - alipayobjects/alicdn:appList 图标;huoshanstatic:xgplayer 演示视频(media-src) +# 启用 Clarity 统计的部署需在 script-src/connect-src/img-src 追加 https://www.clarity.ms https://c.bing.com +add_header Content-Security-Policy-Report-Only "default-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self' 'unsafe-eval' https://*.amap.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://gw.alipayobjects.com https://img.alicdn.com https://*.amap.com https://*.autonavi.com https://tile.openstreetmap.org https://ion.cesium.com https://api.cesium.com https://assets.ion.cesium.com https://*.virtualearth.net; connect-src 'self' blob: https://*.amap.com https://*.autonavi.com https://ion.cesium.com https://api.cesium.com https://assets.ion.cesium.com https://*.virtualearth.net; font-src 'self' data:; media-src 'self' https://sf1-cdn-tos.huoshanstatic.com; worker-src 'self' blob:; frame-src 'self'" always; + +# HSTS:本服务监听 8080 明文(TLS 通常终止在上游 LB)。若本服务直接对外提供 HTTPS, +# 取消注释启用(max-age 按需调整;启用前确认所有子域均可 HTTPS 访问): +# add_header Strict-Transport-Security "max-age=31536000" always; diff --git a/vercel.json b/vercel.json index ef5a748..46f5a47 100644 --- a/vercel.json +++ b/vercel.json @@ -4,6 +4,24 @@ "framework": "umijs", "installCommand": "pnpm install --frozen-lockfile", "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }], + "headers": [ + { + "source": "/(.*)", + "headers": [ + { "key": "X-Content-Type-Options", "value": "nosniff" }, + { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }, + { "key": "X-Frame-Options", "value": "SAMEORIGIN" }, + { + "key": "Permissions-Policy", + "value": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), fullscreen=(self)" + }, + { + "key": "Content-Security-Policy", + "value": "default-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'self'; script-src 'self' 'unsafe-eval' https://*.amap.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://gw.alipayobjects.com https://img.alicdn.com https://*.amap.com https://*.autonavi.com https://tile.openstreetmap.org https://ion.cesium.com https://api.cesium.com https://assets.ion.cesium.com https://*.virtualearth.net; connect-src 'self' blob: https://*.amap.com https://*.autonavi.com https://ion.cesium.com https://api.cesium.com https://assets.ion.cesium.com https://*.virtualearth.net; font-src 'self' data:; media-src 'self' https://sf1-cdn-tos.huoshanstatic.com; worker-src 'self' blob:; frame-src 'self'" + } + ] + } + ], "github": { "silent": true }