From 79adc365d89f7c81bb1b4ef4990b1f83631b09a9 Mon Sep 17 00:00:00 2001 From: josh Date: Sat, 28 Mar 2026 09:20:24 -0400 Subject: [PATCH] =?UTF-8?q?server/server.js=20=E2=80=94=20added=20helmet?= =?UTF-8?q?=20with=20CSP=20configured=20to=20allow=20Google=20Fonts=20Dock?= =?UTF-8?q?erfile=20=E2=80=94=20creates=20a=20non-root=20app=20user=20and?= =?UTF-8?q?=20runs=20the=20process=20under=20it=20server/routes.js=20?= =?UTF-8?q?=E2=80=94=20tailscale=5Fip=20validated=20against=20IPv4=20regex?= =?UTF-8?q?=20(empty=20string=20still=20allowed)=20index.html=20=E2=80=94?= =?UTF-8?q?=20sql.js=20CDN=20script=20tag=20already=20removed=20earlier=20?= =?UTF-8?q?in=20this=20session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 3 ++- Dockerfile | 5 +++++ index.html | 1 - package-lock.json | 16 +++++++++++++--- package.json | 5 +++-- server/routes.js | 3 +++ server/server.js | 10 ++++++++++ 7 files changed, 36 insertions(+), 7 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 59a95d5..8925599 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,8 @@ "permissions": { "allow": [ "Bash(npm test:*)", - "Bash(npm install:*)" + "Bash(npm install:*)", + "Bash(find /c/Users/josh1/Documents/Code/Catalyst -type f \\\\\\(-name *.test.js -o -name *.spec.js -o -name .env* -o -name *.config.js \\\\\\))" ] } } diff --git a/Dockerfile b/Dockerfile index 77fa638..6c6bcd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ FROM node:lts-alpine +RUN addgroup -S app && adduser -S app -G app + WORKDIR /app COPY package*.json ./ @@ -8,5 +10,8 @@ COPY . . RUN awk -F'"' '/"version"/{printf "const VERSION = \"%s\";\n", $4; exit}' \ package.json > js/version.js +RUN chown -R app:app /app +USER app + EXPOSE 3000 CMD ["node", "server/server.js"] diff --git a/index.html b/index.html index 5d1e275..485c45c 100644 --- a/index.html +++ b/index.html @@ -176,7 +176,6 @@ - diff --git a/package-lock.json b/package-lock.json index e8bf6c8..4992715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "catalyst", - "version": "1.0.3", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "catalyst", - "version": "1.0.3", + "version": "1.1.0", "dependencies": { - "express": "^4.18.0" + "express": "^4.18.0", + "helmet": "^8.1.0" }, "devDependencies": { "jsdom": "^25.0.0", @@ -1958,6 +1959,15 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", diff --git a/package.json b/package.json index eb9cea4..6aaae25 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "catalyst", - "version": "1.1.0", + "version": "1.1.1", "type": "module", "scripts": { "start": "node server/server.js", @@ -9,7 +9,8 @@ "version:write": "node -e \"const {version}=JSON.parse(require('fs').readFileSync('package.json','utf8'));require('fs').writeFileSync('js/version.js','const VERSION = \\\"'+version+'\\\";\\n');\"" }, "dependencies": { - "express": "^4.18.0" + "express": "^4.18.0", + "helmet": "^8.1.0" }, "devDependencies": { "jsdom": "^25.0.0", diff --git a/server/routes.js b/server/routes.js index 8b1e199..c75b578 100644 --- a/server/routes.js +++ b/server/routes.js @@ -22,6 +22,9 @@ function validate(body) { errors.push(`state must be one of: ${VALID_STATES.join(', ')}`); if (!VALID_STACKS.includes(body.stack)) errors.push(`stack must be one of: ${VALID_STACKS.join(', ')}`); + const ip = (body.tailscale_ip ?? '').trim(); + if (ip && !/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) + errors.push('tailscale_ip must be a valid IPv4 address or empty'); return errors; } diff --git a/server/server.js b/server/server.js index a09660c..b14ab0f 100644 --- a/server/server.js +++ b/server/server.js @@ -1,4 +1,5 @@ import express from 'express'; +import helmet from 'helmet'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { router } from './routes.js'; @@ -8,6 +9,15 @@ const PORT = process.env.PORT ?? 3000; export const app = express(); +app.use(helmet({ + contentSecurityPolicy: { + directives: { + ...helmet.contentSecurityPolicy.getDefaultDirectives(), + 'style-src': ["'self'", 'https://fonts.googleapis.com'], + 'font-src': ["'self'", 'https://fonts.gstatic.com'], + }, + }, +})); app.use(express.json()); // API