From 308a53530e82c4b556c5af52d7f146400e78f3c0 Mon Sep 17 00:00:00 2001 From: vitya Date: Fri, 28 Aug 2026 00:48:36 +0300 Subject: [PATCH] =?UTF-8?q?sched:=20SECURITY=20=E2=80=94=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BA=D1=80=D1=8B=D1=82=D1=8C=20/mcp=20=D0=B8=20/=20(morda)=20?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=B0;=20au?= =?UTF-8?q?th-=D0=B3=D0=B2=D0=B0=D1=80=D0=B4=20=D0=BD=D0=B0=20=D0=B2=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=20(=D0=BA=D1=80=D0=BE=D0=BC=D0=B5=20/api/hea?= =?UTF-8?q?lth=20+=20static=20UI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vds-kzntsv/sched-custom/Dockerfile.custom | 9 +++++---- .../vds-kzntsv/sched-custom/custom-entry.mjs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/host-stacks/vds-kzntsv/sched-custom/Dockerfile.custom b/host-stacks/vds-kzntsv/sched-custom/Dockerfile.custom index 6b1ccf5..a02c460 100644 --- a/host-stacks/vds-kzntsv/sched-custom/Dockerfile.custom +++ b/host-stacks/vds-kzntsv/sched-custom/Dockerfile.custom @@ -1,9 +1,10 @@ # sched custom single-binary — embedded engine + admin API + UI morda + MCP + storage (MariaDB). # Built on published @schedjs/* npm packages (artifacts that passed the gates). -# Runs apps/daemon/src/custom-entry.mjs — one HTTP server :8080 routing /api, /mcp, / (morda). +# Runs custom-entry.mjs — one HTTP server :8080 routing /api, /mcp, / (morda). +# Auth at the boundary: everything EXCEPT /api/health + static UI assets requires Bearer SCHED_ADMIN_KEY. # -# Build: -# docker build -f apps/daemon/Dockerfile.custom -t registry.kzntsv.site/sched-custom:0.12.1-mysql apps/daemon +# Build (context = this dir): +# docker build -f host-stacks/vds-kzntsv/sched-custom/Dockerfile.custom -t registry.kzntsv.site/sched-custom:0.12.1-mysql host-stacks/vds-kzntsv/sched-custom # Run: # docker run -e MYSQL_URL=mysql://sched:pw@mariadb:3306/sched?ssl={"rejectUnauthorized":false} \ # -e SCHED_ADMIN_KEY=... -e SCHED_TASKS=/app/config/tasks.json \ @@ -21,7 +22,7 @@ RUN echo '{"name":"schedd","private":true,"packageManager":"yarn@4.18.0"}' > pac && yarn cache clean ENV PATH="/app/node_modules/.bin:${PATH}" -COPY src/custom-entry.mjs /app/custom-entry.mjs +COPY custom-entry.mjs /app/custom-entry.mjs # Default tasks.json baked into the image (overridden on prod by a bind mount / SCHED_TASKS). RUN echo '{"tasks":[]}' > /app/tasks.json EXPOSE 8080 diff --git a/host-stacks/vds-kzntsv/sched-custom/custom-entry.mjs b/host-stacks/vds-kzntsv/sched-custom/custom-entry.mjs index 61ec039..779dcda 100644 --- a/host-stacks/vds-kzntsv/sched-custom/custom-entry.mjs +++ b/host-stacks/vds-kzntsv/sched-custom/custom-entry.mjs @@ -51,10 +51,28 @@ const mcpHandler = createMcpHttpHandler({ client: mcpClient, readonly: process.e let uiBundle = null; try { uiBundle = readFileSync(DEFAULT_UI_BUNDLE); } catch { /* bundle missing → morda 404s its script */ } +// One HTTP server, auth at the boundary: everything EXCEPT /api/health (probe, open) and +// /sched-ui.bundle.js (static UI assets) requires `Authorization: Bearer SCHED_ADMIN_KEY`. +// This closes the MCP + admin API + morda exposed WITHOUT a token (vitya found /mcp open). +const AUTH_OPEN_PATHS = ['/api/health']; +function isOpenPath(p) { + return AUTH_OPEN_PATHS.includes(p) || p === '/sched-ui.bundle.js'; +} +function isAuthorized(req) { + if (!ADMIN_KEY) return true; // dev mode, open + const h = req.headers.authorization; + return h === `Bearer ${ADMIN_KEY}`; +} + const server = createServer(async (req, res) => { const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`); const p = url.pathname; try { + if (!isOpenPath(p) && !isAuthorized(req)) { + res.writeHead(401, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ error: 'unauthorized' })); + return; + } if (p === '/api' || p.startsWith('/api/')) { req.url = p.replace(/^\/api/, '') + url.search; // strip /api mount prefix await adminApi.handleRequest(req, res);