server/server.js — added helmet with CSP configured to allow Google Fonts
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
This commit is contained in:
@@ -2,7 +2,8 @@
|
|||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(npm test:*)",
|
"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 \\\\\\))"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
FROM node:lts-alpine
|
FROM node:lts-alpine
|
||||||
|
RUN addgroup -S app && adduser -S app -G app
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
@@ -8,5 +10,8 @@ COPY . .
|
|||||||
RUN awk -F'"' '/"version"/{printf "const VERSION = \"%s\";\n", $4; exit}' \
|
RUN awk -F'"' '/"version"/{printf "const VERSION = \"%s\";\n", $4; exit}' \
|
||||||
package.json > js/version.js
|
package.json > js/version.js
|
||||||
|
|
||||||
|
RUN chown -R app:app /app
|
||||||
|
USER app
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["node", "server/server.js"]
|
CMD ["node", "server/server.js"]
|
||||||
|
|||||||
@@ -176,7 +176,6 @@
|
|||||||
<span id="toast-msg"></span>
|
<span id="toast-msg"></span>
|
||||||
</div>
|
</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/version.js" onerror="window.VERSION=null"></script>
|
||||||
<script src="js/config.js"></script>
|
<script src="js/config.js"></script>
|
||||||
<script src="js/db.js"></script>
|
<script src="js/db.js"></script>
|
||||||
|
|||||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "catalyst",
|
"name": "catalyst",
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "catalyst",
|
"name": "catalyst",
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.0"
|
"express": "^4.18.0",
|
||||||
|
"helmet": "^8.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
@@ -1958,6 +1959,15 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "catalyst",
|
"name": "catalyst",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server/server.js",
|
"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');\""
|
"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": {
|
"dependencies": {
|
||||||
"express": "^4.18.0"
|
"express": "^4.18.0",
|
||||||
|
"helmet": "^8.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ function validate(body) {
|
|||||||
errors.push(`state must be one of: ${VALID_STATES.join(', ')}`);
|
errors.push(`state must be one of: ${VALID_STATES.join(', ')}`);
|
||||||
if (!VALID_STACKS.includes(body.stack))
|
if (!VALID_STACKS.includes(body.stack))
|
||||||
errors.push(`stack must be one of: ${VALID_STACKS.join(', ')}`);
|
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;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import helmet from 'helmet';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { router } from './routes.js';
|
import { router } from './routes.js';
|
||||||
@@ -8,6 +9,15 @@ const PORT = process.env.PORT ?? 3000;
|
|||||||
|
|
||||||
export const app = express();
|
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());
|
app.use(express.json());
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
|||||||
Reference in New Issue
Block a user