From d01e19c90c849d63e526d95e4b9e94608c7a773a Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 01:26:01 +0000 Subject: [PATCH] wip-devsec --- internal/plugin-gallery/dev-plugin.mjs | 19 +++++++++++++------ library/composables/useShadowStyles.js | 14 +++++++++----- vite.config.js | 4 ++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/internal/plugin-gallery/dev-plugin.mjs b/internal/plugin-gallery/dev-plugin.mjs index 6557a8438..3ba4fbfcc 100644 --- a/internal/plugin-gallery/dev-plugin.mjs +++ b/internal/plugin-gallery/dev-plugin.mjs @@ -41,14 +41,15 @@ export function resolveDevAsset(rel, root = REPO_ROOT) { const isFile = (p) => existsSync(p) && statSync(p).isFile() // The built copy lives INSIDE the output folder (public//assets) — public/ holds nothing else // since BUILD-OUTPUT-IS-ONE-FOLDER. The data/ fallback below keeps a fresh clone working unbuilt. - const shipped = resolve(root, 'public', TREE_NAME, 'assets', rel) - if (isFile(shipped)) return shipped - // data/ is the TRACKED source of truth public/assets mirrors (see .gitignore): generate-tree - // writes the mirror, so on a fresh clone — which is what DX receives — public/assets holds almost - // nothing and every table, boundary and people-groups file 404'd under the dev server until the - // first full build. Measured 2026-09-25 across the studio: 13 plugins mounted with empty data. + // data/ FIRST: it is the tracked source of truth that the built copy mirrors. Serving the built copy + // first meant a data/ edit showed OLD data under the dev server until someone rebuilt (73 files have + // both; found by the multiplayer dev-server spike, 2026-09-26). A dev server shows the source. const source = resolve(root, 'data', rel) if (isFile(source)) return source + // The built copy (public//assets) still answers for what exists only there: files generate-tree + // derives rather than copies. + const shipped = resolve(root, 'public', TREE_NAME, 'assets', rel) + if (isFile(shipped)) return shipped const profiles = resolve(root, 'plugin-designs') for (const name of readdirSync(profiles)) { if (name.startsWith('_') || name.startsWith('.')) continue @@ -239,6 +240,12 @@ export function autoSpaDevPlugin(token = '') { configureServer(server) { server.middlewares.use((req, res, next) => { const rawPath = (req.url || '').split('?')[0] + // NEVER THE REPOSITORY ITSELF. Vite serves the project root, so a reachable dev server answered + // /.git/… with 200 — the whole history, and any credential in the remote URL (multiplayer spike, + // 2026-09-26). These routes run before Vite's own checks, so the refusal is here. + let _decoded = rawPath + try { _decoded = decodeURIComponent(rawPath) } catch { /* keep raw */ } + if (/(^|\/)\.(git|env)(\/|$)|(^|\/)\.(merge-queue|main-capture)(\/|$)/i.test(_decoded)) { res.statusCode = 404; return res.end() } // A production embed URL (`/[/]/index.html`) is rewritten onto // the `/__leaf/` route BEFORE any branch runs, so the existing leaf renderer answers it // unchanged — see leafRewrite above for why the dev server must know this shape at all. diff --git a/library/composables/useShadowStyles.js b/library/composables/useShadowStyles.js index 313165d05..649f6591f 100644 --- a/library/composables/useShadowStyles.js +++ b/library/composables/useShadowStyles.js @@ -28,11 +28,13 @@ import { getCurrentInstance, onMounted } from 'vue' * @param {string} [label] - Optional label for the