diff --git a/PROGRESS.md b/PROGRESS.md
index a711e3e..dd23f43 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -30,6 +30,7 @@
- Testing environment wiring added: dedicated namespace (`lomavuokraus-test`), deploy wrapper (`deploy/deploy-test.sh`), API host support, and a DNS updater for `test.lomavuokraus.fi` / `apitest.lomavuokraus.fi`.
- Access control tightened: middleware now gates admin routes, admin-only pages check session/role, API handlers return proper 401/403, and listing removal is limited to owners/admins (no more moderator overrides).
- Security: added OWASP ZAP baseline helper (`scripts/zap-baseline.sh`) and documentation (`docs/security.html`) for quick unauthenticated scans against test/staging/prod.
+- Added master test suite runner (`scripts/run-test-suite.sh`) that executes npm audit, Trivy scan, and ZAP baseline and writes HTML summaries under `reports/runs/`.
- Backend/data: Added Prisma models (User/Listing/ListingTranslation/ListingImage), seed script creates sample listing; DB on Hetzner VM `46.62.203.202`, staging secrets set in `lomavuokraus-web-secrets`.
- Auth: Register/login/verify flows; session cookie (`session_token`), NavBar shows email+role badge. Roles: USER, ADMIN, USER_MODERATOR (approve users), LISTING_MODERATOR (approve listings). Admin can change roles at `/admin/users`.
- Listing flow: create listing (session required), pending/published with admin/moderator approvals; pages for “My listings,” “New listing,” “Profile.” Quick actions tile removed; all actions in navbar.
diff --git a/docs/security.html b/docs/security.html
index bd1e572..f3bff9d 100644
--- a/docs/security.html
+++ b/docs/security.html
@@ -22,6 +22,22 @@
Docker image: owasp/zap2docker-stable (override with ZAP_IMAGE).
+
+ Full test suite
+
+ - Script:
scripts/run-test-suite.sh
+ - Runs:
npm audit (high), Trivy fs scan, ZAP baseline.
+ - Outputs:
reports/runs/<timestamp>/summary.html with links to all tool reports.
+ - Config:
+
+ TARGET: ZAP target URL (default test env).
+ TRIVY_TARGET/TRIVY_MODE: adjust Trivy scope (fs/image).
+ ZAP_IMAGE: override container image if needed.
+
+
+ - Example:
TARGET=https://staging.lomavuokraus.fi TRIVY_MODE=fs ./scripts/run-test-suite.sh
+
+
Auth considerations
diff --git a/scripts/run-test-suite.sh b/scripts/run-test-suite.sh
new file mode 100755
index 0000000..b28e0bf
--- /dev/null
+++ b/scripts/run-test-suite.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Master test/security runner.
+# - npm audit (high+)
+# - Trivy file-system scan (HIGH/CRITICAL)
+# - OWASP ZAP baseline (unauthenticated)
+#
+# Outputs are written under reports/runs// and summarized in summary.html.
+#
+# Env vars:
+# TARGET - URL to scan with ZAP (default: https://test.lomavuokraus.fi)
+# TRIVY_TARGET - Path or image to scan (default: current directory)
+# TRIVY_MODE - "fs" (default) or "image"
+# ZAP_IMAGE - Override ZAP image (default in zap-baseline.sh)
+# TIMEOUT_MINUTES - ZAP timeout minutes (default in zap-baseline.sh)
+
+RUN_TS=$(date +"%Y%m%d-%H%M%S")
+RUN_DIR="reports/runs/${RUN_TS}"
+mkdir -p "$RUN_DIR"
+
+SUMMARY_ROWS=()
+
+log() {
+ echo "[$(date +"%H:%M:%S")] $*"
+}
+
+record_result() {
+ local name="$1"; shift
+ local status="$1"; shift
+ local detail="$1"; shift
+ SUMMARY_ROWS+=("| ${name} | ${status} | ${detail} |
")
+}
+
+# 1) npm audit
+if command -v npm >/dev/null 2>&1; then
+ log "Running npm audit (high)..."
+ AUDIT_JSON="$RUN_DIR/npm-audit.json"
+ AUDIT_TXT="$RUN_DIR/npm-audit.txt"
+ if npm audit --audit-level=high --json >"$AUDIT_JSON" 2>"$AUDIT_TXT"; then
+ record_result "npm audit" "PASS" "text | json"
+ else
+ record_result "npm audit" "FAIL" "text | json"
+ fi
+else
+ log "npm not found; skipping npm audit"
+ record_result "npm audit" "SKIP" "npm not available"
+fi
+
+# 2) Trivy (fs by default)
+TRIVY_TARGET="${TRIVY_TARGET:-.}"
+TRIVY_MODE="${TRIVY_MODE:-fs}"
+if command -v trivy >/dev/null 2>&1; then
+ log "Running Trivy (${TRIVY_MODE}) on ${TRIVY_TARGET}..."
+ TRIVY_TXT="$RUN_DIR/trivy.txt"
+ if trivy "${TRIVY_MODE}" --severity HIGH,CRITICAL --timeout 5m "$TRIVY_TARGET" >"$TRIVY_TXT"; then
+ record_result "Trivy (${TRIVY_MODE})" "PASS" "report"
+ else
+ record_result "Trivy (${TRIVY_MODE})" "FAIL" "report"
+ fi
+else
+ log "Trivy not found; skipping"
+ record_result "Trivy" "SKIP" "trivy not available"
+fi
+
+# 3) OWASP ZAP baseline
+TARGET="${TARGET:-https://test.lomavuokraus.fi}"
+ZAP_DIR="$RUN_DIR/zap"
+mkdir -p "$ZAP_DIR"
+log "Running ZAP baseline against ${TARGET}..."
+if TARGET="$TARGET" REPORT_DIR="$ZAP_DIR" "${BASH_SOURCE%/*}/zap-baseline.sh"; then
+ record_result "OWASP ZAP baseline" "PASS" "HTML | JSON"
+else
+ record_result "OWASP ZAP baseline" "FAIL" "HTML | JSON"
+fi
+
+# Summary HTML
+SUMMARY_FILE="$RUN_DIR/summary.html"
+cat >"$SUMMARY_FILE" <
+
+
+
+ Test Suite Summary - ${RUN_TS}
+
+
+
+ Test Suite Summary
+ Run: ${RUN_TS}
+ Target: ${TARGET}
+
+ | Check | Status | Details |
+
+ ${SUMMARY_ROWS[*]}
+
+
+
+
+EOF
+
+log "Done. Reports in ${RUN_DIR}"
+echo "Open ${SUMMARY_FILE} in a browser for the summary."