- make quick/check/mutation from cj/security-baseline (Makefile unchanged, settings in baseline.mk), pre-commit, gitleaks, Semgrep rules, ESLint security rules, Forgejo workflow - tests/security: generic baseline tests plus app config and role tests against createApp() with an in-memory database - extract createApp() from src/server/index.ts; LB_DATABASE_PATH for tests - fix gaps found by the tests: reject unknown fields on project create, answer malformed JSON with 400 instead of 500, set nosniff on the API - npm audit fix (next 16.4.1 and others, no high/critical left) - clear ESLint errors, safeFileName without control-character regex - per-project ownership stays an open, documented gap (idorGap -> TODO) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
2.8 KiB
TOML
72 lines
2.8 KiB
TOML
# Sicherheits-Mutationen der Leistungsbilanz: jede baut gezielt eine Schutzfunktion aus und
|
|
# muss mindestens einen inhaltlich passenden Test rot machen. Lauf: make mutation
|
|
# Läuft nur die Sicherheitstests (schnell); Node im node:24-Container, falls lokal kein npm.
|
|
|
|
test_cmd = ["sh", "-c", "if command -v npm >/dev/null; then node --import tsx --test tests/security/baseline.test.ts tests/security/roles.test.ts tests/authentication.test.ts; else docker run --rm -u $(id -u):$(id -g) -e HOME=/tmp -v \"$PWD\":/w -w /w node:24 node --import tsx --test tests/security/baseline.test.ts tests/security/roles.test.ts tests/authentication.test.ts; fi"]
|
|
workdir = "."
|
|
|
|
[[mutation]]
|
|
name = "Login-Pflicht für /api entfernt"
|
|
file = "src/server/app.ts"
|
|
old = " app.use(\"/api\", createAuthGuard(authConfig, sessionStore, userRepository));\n"
|
|
new = "\n"
|
|
|
|
[[mutation]]
|
|
name = "Neue ungeschützte Route vor dem Auth-Guard"
|
|
file = "src/server/app.ts"
|
|
old = " app.use(\"/api\", requireSameOriginForWrites());"
|
|
new = " app.get(\"/api/debug\", (_req, res) => {\n res.json({ ok: true });\n });\n app.use(\"/api\", requireSameOriginForWrites());"
|
|
|
|
[[mutation]]
|
|
name = "Admin-Rolle für /api/users entfernt"
|
|
file = "src/server/routes/user.routes.ts"
|
|
old = "userRouter.use(requireRole(\"admin\"));"
|
|
new = ""
|
|
|
|
[[mutation]]
|
|
name = "Passwortwechsel-Pflicht entfernt"
|
|
file = "src/server/app.ts"
|
|
old = " app.use(\"/api\", requirePasswordChanged());\n"
|
|
new = "\n"
|
|
|
|
[[mutation]]
|
|
name = "CSRF-Header-Prüfung entfernt"
|
|
file = "src/server/auth/auth.middleware.ts"
|
|
old = " if (req.get(WRITE_REQUEST_HEADER_NAME) !== WRITE_REQUEST_HEADER_VALUE) {"
|
|
new = " if (false) {"
|
|
|
|
[[mutation]]
|
|
name = "Unbekannte Felder erlaubt (Mass Assignment)"
|
|
file = "src/shared/validation/project-structure.schemas.ts"
|
|
old = " threePhaseVoltageV: z.number().positive().optional(),\n}).strict();"
|
|
new = " threePhaseVoltageV: z.number().positive().optional(),\n});"
|
|
|
|
[[mutation]]
|
|
name = "Kaputtes JSON führt wieder zu 500"
|
|
file = "src/server/middleware/error.middleware.ts"
|
|
old = " if (status !== undefined) {"
|
|
new = " if (false) {"
|
|
|
|
[[mutation]]
|
|
name = "Fehlertext an Client durchgereicht"
|
|
file = "src/server/middleware/error.middleware.ts"
|
|
old = " res.status(500).json({ error: \"Internal Server Error\" });"
|
|
new = " res.status(500).json({ error: String((error as Error).stack) });"
|
|
|
|
[[mutation]]
|
|
name = "CORS für alle Origins"
|
|
file = "src/server/app.ts"
|
|
old = " app.disable(\"x-powered-by\");"
|
|
new = " app.disable(\"x-powered-by\");\n app.use((_req, res, next) => {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n next();\n });"
|
|
|
|
[[mutation]]
|
|
name = "nosniff-Header entfernt"
|
|
file = "src/server/app.ts"
|
|
old = " res.setHeader(\"X-Content-Type-Options\", \"nosniff\");"
|
|
new = ""
|
|
|
|
[[mutation]]
|
|
name = "X-Powered-By wieder an"
|
|
file = "src/server/app.ts"
|
|
old = " app.disable(\"x-powered-by\");"
|
|
new = ""
|