Merge pull request #62 from AnsoCode/fix/csp-coop-and-vite-modulepreload

fix(security): disable COOP header and Vite module-preload polyfill
This commit is contained in:
Anso
2026-03-22 19:51:25 -04:00
committed by GitHub
3 changed files with 12 additions and 0 deletions
+2
View File
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- **COOP header console warning on HTTP deployments:** Helmet sends `Cross-Origin-Opener-Policy: same-origin` by default, which browsers silently ignore over HTTP but log as a console error. Disabled via `crossOriginOpenerPolicy: false` — same rationale as the existing HSTS and COEP disables.
- **Inline script CSP violation from Vite module-preload polyfill:** Vite's production build injects a small inline `<script>` for the module-preload polyfill that was blocked by `script-src 'self'`. Disabled via `build.modulePreload.polyfill: false` in `vite.config.ts` — all modern browsers support `<link rel="modulepreload">` natively.
- **Managed container count wrong when stacks launched from COMPOSE_DIR root:** The `GET /api/stats` endpoint classified containers as managed by matching `com.docker.compose.project` against known stack directory names. When stacks are launched from the COMPOSE_DIR root (rather than each subdirectory), Docker assigns the root folder name as the project name for all of them — causing every such container to appear as "external". Fixed by classifying via `com.docker.compose.project.working_dir`: a container is managed if its working directory falls within COMPOSE_DIR, regardless of the project name.
- **Blank page on HTTP deployments (root cause — Helmet 8 default CSP):** Helmet 8 merges custom `directives` with its built-in defaults, which include `upgrade-insecure-requests`. The previous fix (PR #59) omitted the directive from the custom object, but Helmet silently re-added it from defaults. The correct fix is `upgradeInsecureRequests: null`, which is the Helmet 8 API for explicitly removing a default directive.
- **Login loop caused by remote node auth failure:** When a configured remote node had an expired or invalid API token, every proxied request to that node returned 401. Both `apiFetch` and `fetchForNode` treated any 401 as a user session failure and dispatched `sencho-unauthorized`, immediately logging the user out and sending them back to the login page — even after a successful login. Fixed by adding a `proxyRes` handler that stamps all remote-proxied responses with `x-sencho-proxy: 1`; the frontend now only fires `sencho-unauthorized` when this header is absent (i.e., it's a genuine local session failure).
+3
View File
@@ -76,6 +76,9 @@ const getCookieOptions = (req: Request) => ({
// Setting null is the Helmet 8 API to remove a default directive.
app.use(helmet({
crossOriginEmbedderPolicy: false,
// COOP is only meaningful over HTTPS. Over HTTP the browser logs a warning
// and ignores it, creating noise in the console with no security benefit.
crossOriginOpenerPolicy: false,
hsts: false,
contentSecurityPolicy: {
directives: {
+7
View File
@@ -11,6 +11,13 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"),
},
},
build: {
// Disable the module-preload polyfill inline script.
// Vite injects a small inline <script> for module preloading that violates
// our CSP (script-src 'self' blocks all inline scripts). All modern browsers
// support <link rel="modulepreload"> natively, so the polyfill is unnecessary.
modulePreload: { polyfill: false },
},
server: {
proxy: {
'/api': {