1 Commits

Author SHA1 Message Date
79adc365d8 server/server.js — added helmet with CSP configured to allow Google Fonts
All checks were successful
Build / test (push) Successful in 9m29s
Build / release (push) Successful in 1s
Build / build (push) Successful in 32s
Dockerfile — creates a non-root app user and runs the process under it
server/routes.js — tailscale_ip validated against IPv4 regex (empty string still allowed)
index.html — sql.js CDN script tag already removed earlier in this session
2026-03-28 09:20:24 -04:00
7 changed files with 36 additions and 7 deletions

View File

@@ -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 \\\\\\))"
]
}
}

View File

@@ -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"]

View File

@@ -176,7 +176,6 @@
<span id="toast-msg"></span>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/sql-wasm.js"></script>
<script src="js/version.js" onerror="window.VERSION=null"></script>
<script src="js/config.js"></script>
<script src="js/db.js"></script>

16
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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