From 7280717c9ce38c9b1d6a42b9af20c2c4bcab55a9 Mon Sep 17 00:00:00 2001 From: martin veillette Date: Tue, 11 Aug 2026 08:14:17 -0400 Subject: [PATCH] chore: retrofit template hardening from SceneryStackTemplate Ports portable pieces of OpenPhysics/SceneryStackTemplate@ab309a1: Dependabot override ignores, tilde override pins, security headers, PWA/share metadata, and CLAUDE override docs. --- .github/dependabot.yml | 5 ++++ CLAUDE.md | 16 ++++++++++++ README.md | 2 ++ index.html | 22 ++++++++++++++++ package-lock.json | 6 ++--- package.json | 6 ++--- public/screenshots/narrow.png | Bin 0 -> 29142 bytes public/screenshots/wide.png | Bin 0 -> 26608 bytes scripts/generate-icons.ts | 33 +++++++++++++++++++++--- vite.config.ts | 46 +++++++++++++++++++++++++++++++--- 10 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 public/screenshots/narrow.png create mode 100644 public/screenshots/wide.png diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c67ba08..350b6e7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,6 +18,11 @@ updates: ignore: - dependency-name: "@types/node" update-types: ["version-update:semver-major"] + # Forced in package.json overrides (see CLAUDE.md → package.json overrides). + # Dependabot PRs would fight the pin / reopen known-accepted risk. + - dependency-name: "lodash" + - dependency-name: "three" + - dependency-name: "brace-expansion" groups: development-dependencies: dependency-type: "development" diff --git a/CLAUDE.md b/CLAUDE.md index 72a9e21..9a4c76b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,6 +54,20 @@ Follows the shared [OpenPhysics accessibility convention](https://github.com/Ope **Color pairing gotcha:** `LIGHT_SURFACE_TEXT_FILL` (`controlSurfaceText`, near-black) is only for text on the *white* control surfaces — combo items, flat-button labels, editable field values. Anything drawn on the dark `panelBackground` fill takes `ZenithColors.textColorProperty`. Mixing these up yields ~1:1 contrast and is invisible rather than merely ugly (see the `ObjectNameSearch` result rows). + +### `package.json` overrides + +JSON cannot carry comments, so the rationale for forced transitive pins lives here. Prefer +**tilde (`~`) or exact** versions — caret (`^`) lets minors drift under what is meant to be a +hard pin. Dependabot ignores these three names (see `.github/dependabot.yml`) so it does not +open PRs that fight the overrides. Revisit when SceneryStack drops or re-pins them upstream. + +| Override | Pin | Why | +|---|---|---| +| `lodash` | `~4.18.1` | SceneryStack declares `~4.17.12`. Bump clears Dependabot/npm advisories patched in 4.18.x (e.g. GHSA-r5fr-rjxr-66jc, GHSA-f23m-r3pf-42rh). | +| `three` | `~0.125.2` | SceneryStack declares `^0.104.0`. Floor is 0.125.0 for GHSA-fq6p-x6j3-cmmq (ReDoS). Staying on the 0.125 line avoids a larger API jump; **0.125.x still has open CVEs** (e.g. XSS GHSA-7vvq-7r29-5vg3, fixed only in ≥0.137.0). Remove this override if/when SceneryStack stops depending on `three` or pins a patched line itself. LightPropagation keeps a higher `three` pin — do not force 0.125 there. | +| `brace-expansion` | `~5.0.9` | Transitive via `vite-plugin-pwa` / Workbox. Clears npm audit (originally GHSA-mh99-v99m-4gvg; keep ≥5.0.9 for GHSA-rgw5-rvv9-x895). | + ## Testing Fleet-standard Vitest layout: @@ -85,6 +99,8 @@ Fleet-standard Vitest layout: npm run lint && npm run check && npm run build && npm test ``` +`npm run release` intentionally skips `npm test` in some sims — append `&& npm test` before the version bump so a release cannot ship a failing suite. + ## Development notes **Public deep-link query parameters** (`src/preferences/zenithQueryParameters.ts`): `lat`, `lon`, `date` (ISO-8601 UTC), `fov`, `magLimit`, `showStarLabels`, `showConstellations`, `showPlanetLabels`, `deepStarCatalog`. Example: `?lat=-33.9&lon=151.2&date=2024-12-21T10:00:00Z&fov=90&magLimit=4`. diff --git a/README.md b/README.md index 6ce05c5..7473c96 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Zenith +[![CI](https://github.com/OpenPhysics/Zenith/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenPhysics/Zenith/actions/workflows/ci.yml) + A SceneryStack **first-person planetarium** for the night sky — look around from an Earth observer, set latitude and FOV, and watch stars and planets move with civil time. Built with [SceneryStack](https://scenerystack.org/), diff --git a/index.html b/index.html index 1cc93ec..1f7aa31 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,28 @@ + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 352a41e..1eb90a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4769,9 +4769,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 88ca911..edb9678 100644 --- a/package.json +++ b/package.json @@ -65,8 +65,8 @@ "node": ">=24" }, "overrides": { - "lodash": "^4.18.0", - "three": "^0.125.0", - "brace-expansion": "^5.0.8" + "lodash": "~4.18.1", + "three": "~0.125.2", + "brace-expansion": "~5.0.9" } } diff --git a/public/screenshots/narrow.png b/public/screenshots/narrow.png new file mode 100644 index 0000000000000000000000000000000000000000..5b348295708bddd2dda039b0c73bb657f9342b75 GIT binary patch literal 29142 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 literal 0 HcmV?d00001 diff --git a/public/screenshots/wide.png b/public/screenshots/wide.png new file mode 100644 index 0000000000000000000000000000000000000000..502ae8cdd3facec849c32f45d39e28334024589f GIT binary patch literal 26608 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%0ANv)HR$ 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 sharp(svg, { density }).resize(size, size).png().toBuffer()), ); writeFileSync(resolve(publicDir, "favicon.ico"), await pngToIco(icoBuffers)); + +/** Branded placeholder screenshots for the Web App Manifest `screenshots` member. */ +async function writeScreenshot(width: number, height: number, file: string): Promise { + const iconSize = Math.round(Math.min(width, height) * 0.4); + const icon = await sharp(svg, { density }).resize(iconSize, iconSize).png().toBuffer(); + await sharp({ + create: { + width, + height, + channels: 4, + background: THEME_BG, + }, + }) + .composite([{ input: icon, gravity: "center" }]) + .png() + .toFile(resolve(publicDir, file)); +} + +mkdirSync(resolve(publicDir, "screenshots"), { recursive: true }); +await writeScreenshot(1280, 720, "screenshots/wide.png"); +await writeScreenshot(720, 1280, "screenshots/narrow.png"); diff --git a/vite.config.ts b/vite.config.ts index 2e98975..e00481d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ import { VitePWA } from "vite-plugin-pwa"; * Security headers required for: * - COOP/COEP: SharedArrayBuffer support * - CSP: restrict resource loading to same-origin + known blob/data exceptions + * - Referrer / Permissions: tighten default browser leakage * - X-Content-Type-Options: prevent MIME sniffing * - X-Frame-Options: prevent clickjacking (belt-and-suspenders alongside frame-ancestors) */ @@ -14,9 +15,17 @@ const securityHeaders: Record = { "Cross-Origin-Embedder-Policy": "require-corp", "Content-Security-Policy": [ "default-src 'self'", + // TODO(scenerystack): drop 'unsafe-eval' when SceneryStack no longer needs + + // Function/eval for query-parameter parsing — reopen a CSP audit then. + // 'unsafe-eval' is required for SceneryStack query parameter parsing "script-src 'self' 'unsafe-eval'", "worker-src blob: 'self'", + // TODO(scenerystack): drop 'unsafe-inline' when SceneryStack stops setting + + // element.style / cssText for theming (same CSP revisit as unsafe-eval). + // Inline styles are set via element.style / cssText throughout the UI layer "style-src 'self' 'unsafe-inline'", // data: for icons @@ -29,10 +38,18 @@ const securityHeaders: Record = { "base-uri 'self'", "frame-ancestors 'none'", ].join("; "), + "Referrer-Policy": "strict-origin-when-cross-origin", + "Permissions-Policy": "camera=(), microphone=(), geolocation=()", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", }; +/** Single-file mode: inline every imported asset as base64 (effectively unlimited). */ +const INLINE_LIMIT_BYTES = 100 * 1024 * 1024; + +/** Workbox precache ceiling — SceneryStack bundles exceed the default 2 MB limit. */ +const WORKBOX_MAX_FILE_BYTES = 12 * 1024 * 1024; + /** Escape a string for literal use inside a `RegExp`. */ function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); @@ -130,7 +147,7 @@ export default defineConfig(({ mode }) => { chunkSizeWarningLimit: 5000, ...(single && { // Inline every imported asset as a base64 data URI instead of emitting files. - assetsInlineLimit: 100_000_000, + assetsInlineLimit: INLINE_LIMIT_BYTES, // Emit one CSS file (no per-chunk split) so there is a single tag to inline. cssCodeSplit: false, // Skip copying public/ (favicon, icons) — nothing external should remain. @@ -154,16 +171,20 @@ export default defineConfig(({ mode }) => { registerType: "autoUpdate", includeAssets: ["favicon.ico", "icons/apple-touch-icon.png"], manifest: { + id: "zenith", name: "Zenith", // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys short_name: "Zenith", description: "SceneryStack planetarium renderer for the night sky", + categories: ["education", "science"], // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys theme_color: "#050814", // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys background_color: "#000000", display: "standalone", - orientation: "landscape", + // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys + display_override: ["window-controls-overlay", "standalone"], + // No `orientation` — leave free so portrait-friendly sims are not forced landscape. icons: [ { src: "icons/icon-192.png", @@ -182,9 +203,28 @@ export default defineConfig(({ mode }) => { purpose: "maskable", }, ], + // Placeholder shots from `npm run icons`; replace with real sim screenshots before shipping. + screenshots: [ + { + src: "screenshots/wide.png", + sizes: "1280x720", + type: "image/png", + // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys + form_factor: "wide", + label: "Zenith", + }, + { + src: "screenshots/narrow.png", + sizes: "720x1280", + type: "image/png", + // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys + form_factor: "narrow", + label: "Zenith", + }, + ], }, workbox: { - maximumFileSizeToCacheInBytes: 12 * 1024 * 1024, + maximumFileSizeToCacheInBytes: WORKBOX_MAX_FILE_BYTES, globPatterns: ["**/*.{js,css,html,svg,png,woff2}"], }, }),