From cc8110f50ae722b31cacbe286d482ffd0f10914a Mon Sep 17 00:00:00 2001 From: martin veillette Date: Tue, 11 Aug 2026 08:11:31 -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 -> 37993 bytes public/screenshots/wide.png | Bin 0 -> 35863 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 b62f914..8b0d910 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,6 +109,20 @@ Full convention: [Baton/ACCESSIBILITY.md](https://github.com/OpenPhysics/Baton/b flat list of exports. Four screens' worth of names is more than one flat list keeps legible; Baton CONVENTIONS §2 permits this variation, and the file still lives at `src/` root as required. + +### `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 `npm test` — Vitest, `happy-dom`, `--expose-gc`. 151 tests across seven files. @@ -135,6 +149,8 @@ npm run test:fuzz:quick bash ../Baton/scripts/check-repo-compliance.sh SpecialRelativity # from the workspace root ``` +`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. + Query parameters: `?initialBeta=0.8`, `?showRapidity=true`, `?shadeLightCone=true`. ## PWA diff --git a/README.md b/README.md index 4bb5bbc..95ec9bf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Special Relativity +[![CI](https://github.com/OpenPhysics/SpecialRelativity/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenPhysics/SpecialRelativity/actions/workflows/ci.yml) + An interactive simulation of flat-spacetime relativity — a moving light clock, a live Minkowski diagram, the ladder-and-barn paradox, the twin paradox, and the relativistic Doppler effect — built with diff --git a/index.html b/index.html index f33dc56..02df0aa 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,28 @@ + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index bc40a0d..02acf9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4701,9 +4701,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 fbff3af..42ede5e 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,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..9fa3173ea2f4618ba6333f9df97ced1b95c9f06c GIT binary patch literal 37993 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 Hr-UW|)RO{h literal 0 HcmV?d00001 diff --git a/scripts/generate-icons.ts b/scripts/generate-icons.ts index 25cb167..eca966a 100644 --- a/scripts/generate-icons.ts +++ b/scripts/generate-icons.ts @@ -1,11 +1,14 @@ /** * generate-icons.ts * - * Rasterizes public/icons/icon.svg into the PNG icons and favicon.ico used by the PWA. - * Run with: npm run icons + * Rasterizes public/icons/icon.svg into the PNG icons, favicon.ico, and placeholder + * PWA install screenshots used by the manifest. Run with: npm run icons + * + * Replace public/screenshots/{wide,narrow}.png with real sim shots before shipping + * (e.g. Baton/scripts/generate-screenshots.sh → copy into public/screenshots/). */ -import { readFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import pngToIco from "png-to-ico"; @@ -15,6 +18,9 @@ const here = dirname(fileURLToPath(import.meta.url)); const publicDir = resolve(here, "..", "public"); const svg = readFileSync(resolve(publicDir, "icons", "icon.svg")); +/** Theme background matching `theme_color` / icon.svg fill (`#1a1a2e`). */ +const THEME_BG = { r: 26, g: 26, b: 46, alpha: 1 }; + const density = 512; const pngTargets = [ @@ -31,3 +37,24 @@ const icoBuffers = await Promise.all( [16, 32, 48, 64].map((size) => 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 433ac13..39e8707 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: "special-relativity", name: "Special Relativity", // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys short_name: "SpecialRelativity", description: "A SceneryStack simulation: Special Relativity", + categories: ["education", "science"], // biome-ignore lint/style/useNamingConvention: Web App Manifest spec requires snake_case keys theme_color: "#1a1a2e", // 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: "Special Relativity", + }, + { + 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: "Special Relativity", + }, + ], }, workbox: { - maximumFileSizeToCacheInBytes: 12 * 1024 * 1024, + maximumFileSizeToCacheInBytes: WORKBOX_MAX_FILE_BYTES, globPatterns: ["**/*.{js,css,html,svg,png,woff2}"], }, }),