fix(quick-261009-p0m): Review-Befunde WR-01/02/04/05, IN-01/02/03/04 (Abbild-Bau, Semgrep im Container, Zeitbudget, ZAP-Haken)
- WR-01: prisma generate als eigener Schritt mit Pruefung im api-Abbild, Bau scheitert ohne Client; Startprobe als .gitea/scripts/image-start-check.sh versioniert - WR-02: Semgrep im offiziellen Container, per Digest angepinnt (kein pipx/PyPI mehr) - WR-04: Zeitbudget 1500 s, Limits je Werkzeug, Ergebniszeile sofort nach jedem Werkzeug - WR-05: Authorization-Ersetzung der ZAP-Pruefung nur fuer das Ziel - IN-01: yarn/npm per Platzhalter entfernt und Abwesenheit geprueft (api und web) - IN-02: Kommentar zu doppelten Kopfzeilen in next.config.ts richtiggestellt - IN-03: Berichtsordner Modus 700, Zugangsdaten nur als base64 (keine curl-Konfiguration) - IN-04: flacher Klon wird bei gitleaks als unvollstaendig gemeldet Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+86
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# image-start-check.sh -- Startprobe fuer das Server-Abbild (quick-261009-p0m).
|
||||||
|
#
|
||||||
|
# Beweist, dass das fertig gebaute api-Abbild (ohne Entwicklungswerkzeuge) gegen eine
|
||||||
|
# FRISCHE, leere Datenbank alle Migrationen anwendet und die API startet. Das faengt
|
||||||
|
# genau den Fehler ab, den ein Bau allein nicht zeigt: ein Abbild, dem der Prisma-Client
|
||||||
|
# oder ein Laufzeitpaket fehlt, wird gebaut und veroeffentlicht, stirbt aber beim Start.
|
||||||
|
#
|
||||||
|
# Aufruf (aus dem Repository-Wurzelverzeichnis, Docker noetig):
|
||||||
|
# bash .gitea/scripts/image-start-check.sh # lokales Abbild tessera-ctl-api:latest
|
||||||
|
# bash .gitea/scripts/image-start-check.sh localhost:3002/schalli/tessera-ctl/api:beta
|
||||||
|
#
|
||||||
|
# Es startet ein kurzlebiges Postgres 16 und das Abbild in einem eigenen Docker-Netz, mit
|
||||||
|
# reinen Testwerten (keine echten Zugangsdaten, kein Zugriff auf Tessera oder alpha).
|
||||||
|
# Alles Angelegte wird beim Beenden wieder entfernt. Bei Erfolg endet die Ausgabe mit
|
||||||
|
# "frische-datenbank ok" und Exit 0, sonst mit einer Fehlermeldung, dem Ende des
|
||||||
|
# API-Protokolls und Exit 1. Wird nicht automatisch von der Pipeline aufgerufen; es
|
||||||
|
# gehoert in die Freigabe-Checkliste (docs/anleitung-betrieb.md, docs/anleitung-entwicklung.md).
|
||||||
|
set -u
|
||||||
|
|
||||||
|
IMAGE="${1:-tessera-ctl-api:latest}"
|
||||||
|
ID="start-check-$$"
|
||||||
|
NET="$ID-net"
|
||||||
|
DB="$ID-db"
|
||||||
|
API="$ID-api"
|
||||||
|
LOG="$(mktemp)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
docker rm -f "$API" "$DB" >/dev/null 2>&1
|
||||||
|
docker network rm "$NET" >/dev/null 2>&1
|
||||||
|
rm -f "$LOG"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM HUP PIPE
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "FEHLER: $*" >&2
|
||||||
|
echo "--- Protokoll der API (letzte 40 Zeilen) ---" >&2
|
||||||
|
docker logs "$API" 2>&1 | tail -40 >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
docker network create "$NET" >/dev/null || { echo "FEHLER: Netz nicht anlegbar" >&2; exit 1; }
|
||||||
|
docker run -d --name "$DB" --network "$NET" \
|
||||||
|
-e POSTGRES_USER=tessera -e POSTGRES_PASSWORD=tessera_test -e POSTGRES_DB=tessera \
|
||||||
|
postgres:16-alpine >/dev/null || { echo "FEHLER: Datenbank startet nicht" >&2; exit 1; }
|
||||||
|
|
||||||
|
i=0
|
||||||
|
until docker exec "$DB" pg_isready -U tessera -d tessera >/dev/null 2>&1; do
|
||||||
|
i=$((i + 1))
|
||||||
|
[ "$i" -gt 60 ] && { echo "FEHLER: Datenbank wird nicht bereit" >&2; exit 1; }
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
# pg_isready meldet schon waehrend der Einrichtung "bereit"; kurz warten, dann
|
||||||
|
# eine echte Abfrage (die Einrichtung startet den Server einmal neu).
|
||||||
|
sleep 3
|
||||||
|
i=0
|
||||||
|
until docker exec "$DB" psql -U tessera -d tessera -tAc 'select 1' >/dev/null 2>&1; do
|
||||||
|
i=$((i + 1))
|
||||||
|
[ "$i" -gt 30 ] && { echo "FEHLER: Datenbank antwortet nicht" >&2; exit 1; }
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
KEY="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
|
||||||
|
docker run -d --name "$API" --network "$NET" \
|
||||||
|
-e "DATABASE_URL=postgresql://tessera:tessera_test@$DB:5432/tessera" \
|
||||||
|
-e "TESSERA_ENCRYPTION_KEY=$KEY" \
|
||||||
|
-e "JWT_SECRET=fresh-db-test-jwt-secret" \
|
||||||
|
-e TESSERA_ADMIN_USER=admin -e TESSERA_ADMIN_EMAIL=admin@tessera.local \
|
||||||
|
-e TESSERA_ADMIN_PASSWORD=fresh-db-test-pw -e TESSERA_FORCE_CHANGE=false \
|
||||||
|
"$IMAGE" >/dev/null || { echo "FEHLER: API startet nicht" >&2; exit 1; }
|
||||||
|
|
||||||
|
i=0
|
||||||
|
while :; do
|
||||||
|
docker logs "$API" >"$LOG" 2>&1
|
||||||
|
grep -q "Tessera API running" "$LOG" && break
|
||||||
|
[ "$(docker inspect -f '{{.State.Running}}' "$API" 2>/dev/null)" = "true" ] || fail "Container ist beendet, bevor die Startzeile kam"
|
||||||
|
i=$((i + 1))
|
||||||
|
[ "$i" -gt 120 ] && fail "keine Startzeile nach 120 Sekunden"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
grep -qi "successfully applied" "$LOG" || fail "Prisma meldet nicht, dass die Migrationen angewendet wurden"
|
||||||
|
N="$(docker exec "$DB" psql -U tessera -d tessera -tAc "select count(*) from _prisma_migrations where finished_at is not null")"
|
||||||
|
[ "${N:-0}" -ge 1 ] || fail "keine abgeschlossene Migration in der Datenbank"
|
||||||
|
echo "migrationen=$N"
|
||||||
|
echo "frische-datenbank ok"
|
||||||
+233
-127
@@ -11,7 +11,8 @@
|
|||||||
# gitleaks gesamte Git-Historie auf Zugangsdaten (--redact, Ausnahmen in .gitleaks.toml)
|
# gitleaks gesamte Git-Historie auf Zugangsdaten (--redact, Ausnahmen in .gitleaks.toml)
|
||||||
# pnpm audit bekannte Schwachstellen der Produktions-Abhaengigkeiten
|
# pnpm audit bekannte Schwachstellen der Produktions-Abhaengigkeiten
|
||||||
# osv-scanner pnpm-lock.yaml und apps/desktop/src-tauri/Cargo.lock
|
# osv-scanner pnpm-lock.yaml und apps/desktop/src-tauri/Cargo.lock
|
||||||
# semgrep statische Code-Analyse (Ausnahmen in .semgrepignore)
|
# semgrep statische Code-Analyse (Ausnahmen in .semgrepignore); laeuft im
|
||||||
|
# offiziellen Semgrep-Abbild, das per Digest angepinnt ist
|
||||||
# trivy fs Abhaengigkeiten, Dockerfile-Einstellungen, Zugangsdaten im Quellstand
|
# trivy fs Abhaengigkeiten, Dockerfile-Einstellungen, Zugangsdaten im Quellstand
|
||||||
# trivy image die frisch gebauten Abbilder api und web (nur main und Tags v*)
|
# trivy image die frisch gebauten Abbilder api und web (nur main und Tags v*)
|
||||||
#
|
#
|
||||||
@@ -44,6 +45,15 @@
|
|||||||
# Werkzeug-Versionen anheben: neue Version UND die SHA256-Summe aus der offiziellen
|
# Werkzeug-Versionen anheben: neue Version UND die SHA256-Summe aus der offiziellen
|
||||||
# Pruefsummendatei der Veroeffentlichung unten eintragen (docs/ci-cd-setup.md).
|
# Pruefsummendatei der Veroeffentlichung unten eintragen (docs/ci-cd-setup.md).
|
||||||
#
|
#
|
||||||
|
# Zeitplan: Der Job der Pipeline hat 30 Minuten (timeout-minutes). Das Skript haelt sich
|
||||||
|
# selbst an ein Gesamtbudget von 25 Minuten (BUDGET_SECONDS = 1500) und gibt die Zeile
|
||||||
|
# `SECURITY-SUMMARY ...` JE WERKZEUG aus, sobald es fertig ist -- bricht der Runner den
|
||||||
|
# Job doch einmal ab, stehen die bis dahin fertigen Ergebnisse im Protokoll. Jedes
|
||||||
|
# Werkzeug hat ein eigenes Zeitlimit (T_*); die Limits ergeben zusammen hoechstens 1500 s,
|
||||||
|
# und `tmo` kuerzt jedes Limit auf die noch verbleibende Gesamtzeit. Ein Werkzeug, das
|
||||||
|
# sein Limit reisst, wird als `skipped reason=zeitgrenze` gemeldet, eines, fuer das das
|
||||||
|
# Gesamtbudget nicht mehr reicht, als `skipped reason=gesamtbudget`.
|
||||||
|
#
|
||||||
# Dieses Skript kennt kein Secret und gibt keines aus: Zeilen und Zusammenfassung
|
# Dieses Skript kennt kein Secret und gibt keines aus: Zeilen und Zusammenfassung
|
||||||
# enthalten nur Zaehlwerte und Statuswoerter.
|
# enthalten nur Zaehlwerte und Statuswoerter.
|
||||||
|
|
||||||
@@ -62,9 +72,27 @@ OSV_VERSION=2.6.0
|
|||||||
OSV_URL="https://github.com/google/osv-scanner/releases/download/v2.6.0/osv-scanner_linux_amd64"
|
OSV_URL="https://github.com/google/osv-scanner/releases/download/v2.6.0/osv-scanner_linux_amd64"
|
||||||
OSV_SHA256=ca69b3d3cd08f889a49dc0a383122f71cc528b83803671df5fd874d97485b108
|
OSV_SHA256=ca69b3d3cd08f889a49dc0a383122f71cc528b83803671df5fd874d97485b108
|
||||||
|
|
||||||
|
# Semgrep laeuft als Container, angepinnt per Digest (Index des Abbilds semgrep/semgrep:1.180.0,
|
||||||
|
# 2026-10-09 geholt). Docker prueft beim Laden, dass der Inhalt zum Digest passt; kein
|
||||||
|
# Paketindex, keine frei aufgeloesten Abhaengigkeiten.
|
||||||
SEMGREP_VERSION=1.180.0
|
SEMGREP_VERSION=1.180.0
|
||||||
|
SEMGREP_IMAGE=semgrep/semgrep@sha256:529ee8a277ec8adc5b534d7c74eea0a47e9de21d62852b6ba7ac6ba9566845c3
|
||||||
PNPM_VERSION=9.15.0
|
PNPM_VERSION=9.15.0
|
||||||
|
|
||||||
|
# Zeitplan in Sekunden (siehe Kopf). Summe Installation: 3 x 60 + 150 + 45 = 375;
|
||||||
|
# Summe Pruefungen: 180 + 90 + 120 + 300 + 180 + 2 x 120 = 1110; zusammen 1485 <= 1500.
|
||||||
|
BUDGET_SECONDS=1500
|
||||||
|
T_DOWNLOAD=60
|
||||||
|
T_SEMGREP_PULL=150
|
||||||
|
T_COREPACK=45
|
||||||
|
T_GITLEAKS=180
|
||||||
|
T_AUDIT=90
|
||||||
|
T_OSV=120
|
||||||
|
T_SEMGREP=300
|
||||||
|
T_TRIVY_FS=180
|
||||||
|
T_TRIVY_IMAGE=120
|
||||||
|
START_EPOCH=$(date +%s)
|
||||||
|
|
||||||
REGISTRY="${REGISTRY:-localhost:3002/schalli/tessera-ctl}"
|
REGISTRY="${REGISTRY:-localhost:3002/schalli/tessera-ctl}"
|
||||||
REF="${GITHUB_REF:-}"
|
REF="${GITHUB_REF:-}"
|
||||||
MODE="${1:-}"
|
MODE="${1:-}"
|
||||||
@@ -98,9 +126,6 @@ esac
|
|||||||
GITLEAKS_BIN="$TOOLDIR/gitleaks-$GITLEAKS_VERSION/gitleaks"
|
GITLEAKS_BIN="$TOOLDIR/gitleaks-$GITLEAKS_VERSION/gitleaks"
|
||||||
TRIVY_BIN="$TOOLDIR/trivy-$TRIVY_VERSION/trivy"
|
TRIVY_BIN="$TOOLDIR/trivy-$TRIVY_VERSION/trivy"
|
||||||
OSV_BIN="$TOOLDIR/osv-scanner-$OSV_VERSION/osv-scanner"
|
OSV_BIN="$TOOLDIR/osv-scanner-$OSV_VERSION/osv-scanner"
|
||||||
PIPX_HOME_DIR="$TOOLDIR/pipx"
|
|
||||||
PIPX_BIN_PATH="$TOOLDIR/pipx-bin"
|
|
||||||
SEMGREP_BIN="$PIPX_BIN_PATH/semgrep"
|
|
||||||
COREPACK_HOME_DIR="$TOOLDIR/corepack"
|
COREPACK_HOME_DIR="$TOOLDIR/corepack"
|
||||||
TRIVY_CACHE="$TOOLDIR/trivy-cache"
|
TRIVY_CACHE="$TOOLDIR/trivy-cache"
|
||||||
|
|
||||||
@@ -125,7 +150,8 @@ print_plan() {
|
|||||||
echo "werkzeug gitleaks $GITLEAKS_VERSION"
|
echo "werkzeug gitleaks $GITLEAKS_VERSION"
|
||||||
echo "werkzeug trivy $TRIVY_VERSION"
|
echo "werkzeug trivy $TRIVY_VERSION"
|
||||||
echo "werkzeug osv-scanner $OSV_VERSION"
|
echo "werkzeug osv-scanner $OSV_VERSION"
|
||||||
echo "werkzeug semgrep $SEMGREP_VERSION"
|
echo "werkzeug semgrep $SEMGREP_VERSION (Abbild $SEMGREP_IMAGE)"
|
||||||
|
echo "zeitbudget ${BUDGET_SECONDS}s"
|
||||||
echo "werkzeug pnpm $PNPM_VERSION"
|
echo "werkzeug pnpm $PNPM_VERSION"
|
||||||
echo "werkzeugordner $TOOLDIR"
|
echo "werkzeugordner $TOOLDIR"
|
||||||
if [ -n "$IMAGES" ]; then
|
if [ -n "$IMAGES" ]; then
|
||||||
@@ -153,17 +179,27 @@ esac
|
|||||||
|
|
||||||
# --- Hilfsfunktionen ---------------------------------------------------------
|
# --- Hilfsfunktionen ---------------------------------------------------------
|
||||||
WORK=""
|
WORK=""
|
||||||
|
SEMGREP_CT=""
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
[ -n "$SEMGREP_CT" ] && docker rm -f "$SEMGREP_CT" >/dev/null 2>&1
|
||||||
[ -n "$WORK" ] && rm -rf "$WORK"
|
[ -n "$WORK" ] && rm -rf "$WORK"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
trap 'exit 0' INT TERM
|
trap 'exit 0' INT TERM
|
||||||
|
|
||||||
# Laufzeitgrenze je Werkzeug, falls `timeout` vorhanden ist.
|
# Laufzeitgrenze je Werkzeug, falls `timeout` vorhanden ist. Das Limit wird auf die noch
|
||||||
|
# verbleibende Gesamtzeit gekuerzt; ist das Gesamtbudget aufgebraucht, laeuft nichts mehr
|
||||||
|
# (Rueckgabe 97). Rueckgabe 124 = Limit gerissen (von `timeout`).
|
||||||
|
BUDGET_RC=97
|
||||||
tmo() {
|
tmo() {
|
||||||
_secs="$1"
|
_secs="$1"
|
||||||
shift
|
shift
|
||||||
|
_left=$((START_EPOCH + BUDGET_SECONDS - $(date +%s)))
|
||||||
|
if [ "$_left" -le 5 ]; then
|
||||||
|
return "$BUDGET_RC"
|
||||||
|
fi
|
||||||
|
[ "$_secs" -gt "$_left" ] && _secs="$_left"
|
||||||
if command -v timeout >/dev/null 2>&1; then
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
timeout "$_secs" "$@"
|
timeout "$_secs" "$@"
|
||||||
else
|
else
|
||||||
@@ -181,6 +217,22 @@ setstatus() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Rueckgabewert eines Werkzeugs auswerten: Zeitgrenze oder Gesamtbudget -> Statuswort,
|
||||||
|
# ein evtl. halbfertiger Bericht wird verworfen. $1 Rueckgabewert, $2 Werkzeug, $3 Bericht.
|
||||||
|
note_rc() {
|
||||||
|
case "$1" in
|
||||||
|
124)
|
||||||
|
setstatus "$2" zeitgrenze
|
||||||
|
rm -f "$3"
|
||||||
|
;;
|
||||||
|
"$BUDGET_RC")
|
||||||
|
setstatus "$2" gesamtbudget
|
||||||
|
rm -f "$3"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
sha_ok() {
|
sha_ok() {
|
||||||
# $1 Datei, $2 erwartete SHA256
|
# $1 Datei, $2 erwartete SHA256
|
||||||
[ -f "$1" ] || return 1
|
[ -f "$1" ] || return 1
|
||||||
@@ -195,7 +247,7 @@ fetch_verified() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
rm -f "$4" "$4.part"
|
rm -f "$4" "$4.part"
|
||||||
if ! curl -fsSL --retry 2 --connect-timeout 20 --max-time 600 -o "$4.part" "$2" 2>/dev/null; then
|
if ! curl -fsSL --retry 2 --connect-timeout 20 --max-time "$T_DOWNLOAD" -o "$4.part" "$2" 2>/dev/null; then
|
||||||
rm -f "$4.part"
|
rm -f "$4.part"
|
||||||
FETCH_REASON=download
|
FETCH_REASON=download
|
||||||
return 1
|
return 1
|
||||||
@@ -252,18 +304,17 @@ install_osv() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_semgrep() {
|
install_semgrep() {
|
||||||
if [ -x "$SEMGREP_BIN" ] && "$SEMGREP_BIN" --version 2>/dev/null | grep -q "^$SEMGREP_VERSION"; then
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
echo "Werkzeug semgrep $SEMGREP_VERSION bereit (vorhanden)"
|
skipline semgrep kein-docker
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if ! command -v pipx >/dev/null 2>&1; then
|
if docker image inspect "$SEMGREP_IMAGE" >/dev/null 2>&1; then
|
||||||
skipline semgrep kein-pipx
|
echo "Werkzeug semgrep $SEMGREP_VERSION bereit (Abbild vorhanden, Digest angepinnt)"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
mkdir -p "$PIPX_HOME_DIR" "$PIPX_BIN_PATH" 2>/dev/null
|
if tmo "$T_SEMGREP_PULL" docker pull -q "$SEMGREP_IMAGE" >/dev/null 2>&1 &&
|
||||||
if PIPX_HOME="$PIPX_HOME_DIR" PIPX_BIN_DIR="$PIPX_BIN_PATH" tmo 600 pipx install --force "semgrep==$SEMGREP_VERSION" >/dev/null 2>&1 &&
|
docker image inspect "$SEMGREP_IMAGE" >/dev/null 2>&1; then
|
||||||
"$SEMGREP_BIN" --version 2>/dev/null | grep -q "^$SEMGREP_VERSION"; then
|
echo "Werkzeug semgrep $SEMGREP_VERSION bereit (Abbild geladen, Digest angepinnt)"
|
||||||
echo "Werkzeug semgrep $SEMGREP_VERSION bereit"
|
|
||||||
else
|
else
|
||||||
skipline semgrep install
|
skipline semgrep install
|
||||||
fi
|
fi
|
||||||
@@ -271,7 +322,11 @@ install_semgrep() {
|
|||||||
|
|
||||||
# pnpm: eine pnpm 9.15.x im PATH, sonst corepack mit angepinnter Version.
|
# pnpm: eine pnpm 9.15.x im PATH, sonst corepack mit angepinnter Version.
|
||||||
PNPM_MODE=""
|
PNPM_MODE=""
|
||||||
|
PNPM_CHECKED=no
|
||||||
detect_pnpm() {
|
detect_pnpm() {
|
||||||
|
[ "$PNPM_CHECKED" = yes ] && [ -n "$PNPM_MODE" ] && return 0
|
||||||
|
[ "$PNPM_CHECKED" = yes ] && return 1
|
||||||
|
PNPM_CHECKED=yes
|
||||||
PNPM_MODE=""
|
PNPM_MODE=""
|
||||||
if command -v pnpm >/dev/null 2>&1; then
|
if command -v pnpm >/dev/null 2>&1; then
|
||||||
_v="$(cd "$TOOLDIR" 2>/dev/null && pnpm --version 2>/dev/null)"
|
_v="$(cd "$TOOLDIR" 2>/dev/null && pnpm --version 2>/dev/null)"
|
||||||
@@ -283,7 +338,7 @@ detect_pnpm() {
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
if command -v corepack >/dev/null 2>&1; then
|
if command -v corepack >/dev/null 2>&1; then
|
||||||
_v="$(COREPACK_HOME="$COREPACK_HOME_DIR" COREPACK_ENABLE_DOWNLOAD_PROMPT=0 tmo 120 corepack "pnpm@$PNPM_VERSION" --version 2>/dev/null)"
|
_v="$(COREPACK_HOME="$COREPACK_HOME_DIR" COREPACK_ENABLE_DOWNLOAD_PROMPT=0 tmo "$T_COREPACK" corepack "pnpm@$PNPM_VERSION" --version 2>/dev/null)"
|
||||||
if [ "$_v" = "$PNPM_VERSION" ]; then
|
if [ "$_v" = "$PNPM_VERSION" ]; then
|
||||||
PNPM_MODE=corepack
|
PNPM_MODE=corepack
|
||||||
return 0
|
return 0
|
||||||
@@ -295,9 +350,9 @@ detect_pnpm() {
|
|||||||
# pnpm im aktuellen Ordner ausfuehren (nach detect_pnpm).
|
# pnpm im aktuellen Ordner ausfuehren (nach detect_pnpm).
|
||||||
run_pnpm() {
|
run_pnpm() {
|
||||||
if [ "$PNPM_MODE" = path ]; then
|
if [ "$PNPM_MODE" = path ]; then
|
||||||
tmo 300 pnpm "$@"
|
tmo "$T_AUDIT" pnpm "$@"
|
||||||
else
|
else
|
||||||
COREPACK_HOME="$COREPACK_HOME_DIR" COREPACK_ENABLE_DOWNLOAD_PROMPT=0 tmo 300 corepack "pnpm@$PNPM_VERSION" "$@"
|
COREPACK_HOME="$COREPACK_HOME_DIR" COREPACK_ENABLE_DOWNLOAD_PROMPT=0 tmo "$T_AUDIT" corepack "pnpm@$PNPM_VERSION" "$@"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,60 +377,98 @@ do_install() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- Pruefungen --------------------------------------------------------------
|
# --- Pruefungen --------------------------------------------------------------
|
||||||
|
# Jede Pruefung gibt ihre Zeile `SECURITY-SUMMARY ...` aus, sobald sie fertig ist
|
||||||
|
# (report_tool), damit ein Abbruch des ganzen Jobs die fertigen Ergebnisse nicht verschluckt.
|
||||||
scan_gitleaks() {
|
scan_gitleaks() {
|
||||||
if [ ! -x "$GITLEAKS_BIN" ]; then
|
if [ ! -x "$GITLEAKS_BIN" ]; then
|
||||||
setstatus gitleaks nicht-installiert
|
setstatus gitleaks nicht-installiert
|
||||||
|
report_tool gitleaks
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
setstatus gitleaks fehler
|
# Ein flacher Klon enthaelt nur einen Teil der Historie: das Ergebnis waere "sauber",
|
||||||
|
# ohne dass alle Commits geprueft wurden. Der Lauf wird trotzdem gemacht, aber als
|
||||||
|
# unvollstaendig gemeldet.
|
||||||
|
if [ "$(git -C "$ROOT" rev-parse --is-shallow-repository 2>/dev/null)" = true ]; then
|
||||||
|
setstatus gitleaks flacher-klon
|
||||||
|
else
|
||||||
|
setstatus gitleaks fehler
|
||||||
|
fi
|
||||||
(
|
(
|
||||||
cd "$ROOT" &&
|
cd "$ROOT" &&
|
||||||
tmo 900 "$GITLEAKS_BIN" git --config "$ROOT/.gitleaks.toml" --redact --no-banner \
|
tmo "$T_GITLEAKS" "$GITLEAKS_BIN" git --config "$ROOT/.gitleaks.toml" --redact --no-banner \
|
||||||
--exit-code 0 --report-format json --report-path "$REPORT/gitleaks.json" . >/dev/null 2>&1
|
--exit-code 0 --report-format json --report-path "$REPORT/gitleaks.json" . >/dev/null 2>&1
|
||||||
)
|
)
|
||||||
|
note_rc $? gitleaks "$REPORT/gitleaks.json"
|
||||||
|
report_tool gitleaks
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_pnpm_audit() {
|
scan_pnpm_audit() {
|
||||||
if ! detect_pnpm; then
|
if ! detect_pnpm; then
|
||||||
setstatus pnpm-audit-prod kein-pnpm
|
setstatus pnpm-audit-prod kein-pnpm
|
||||||
|
report_tool pnpm-audit-prod
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
setstatus pnpm-audit-prod fehler
|
setstatus pnpm-audit-prod fehler
|
||||||
(
|
(
|
||||||
cd "$WORK/src" && run_pnpm audit --prod --json >"$REPORT/pnpm-audit-prod.json" 2>/dev/null
|
cd "$WORK/src" && run_pnpm audit --prod --json >"$REPORT/pnpm-audit-prod.json" 2>/dev/null
|
||||||
)
|
)
|
||||||
|
note_rc $? pnpm-audit-prod "$REPORT/pnpm-audit-prod.json"
|
||||||
|
report_tool pnpm-audit-prod
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_osv() {
|
scan_osv() {
|
||||||
if [ ! -x "$OSV_BIN" ]; then
|
if [ ! -x "$OSV_BIN" ]; then
|
||||||
setstatus osv-scanner nicht-installiert
|
setstatus osv-scanner nicht-installiert
|
||||||
|
report_tool osv-scanner
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
setstatus osv-scanner fehler
|
setstatus osv-scanner fehler
|
||||||
(
|
(
|
||||||
cd "$WORK/src" &&
|
cd "$WORK/src" &&
|
||||||
tmo 600 "$OSV_BIN" scan source --lockfile pnpm-lock.yaml \
|
tmo "$T_OSV" "$OSV_BIN" scan source --lockfile pnpm-lock.yaml \
|
||||||
--lockfile apps/desktop/src-tauri/Cargo.lock \
|
--lockfile apps/desktop/src-tauri/Cargo.lock \
|
||||||
--format json --output "$REPORT/osv-scanner.json" >/dev/null 2>&1
|
--format json --output "$REPORT/osv-scanner.json" >/dev/null 2>&1
|
||||||
)
|
)
|
||||||
|
note_rc $? osv-scanner "$REPORT/osv-scanner.json"
|
||||||
|
report_tool osv-scanner
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Semgrep im angepinnten Container: der Quellstand wird mit `docker cp` hinein und der
|
||||||
|
# Bericht heraus kopiert (kein Ordner-Mount, denn der Pfad der Pipeline liegt im
|
||||||
|
# Job-Container, nicht auf dem Docker-Rechner).
|
||||||
scan_semgrep() {
|
scan_semgrep() {
|
||||||
if [ ! -x "$SEMGREP_BIN" ]; then
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
setstatus semgrep kein-docker
|
||||||
|
report_tool semgrep
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! docker image inspect "$SEMGREP_IMAGE" >/dev/null 2>&1; then
|
||||||
setstatus semgrep nicht-installiert
|
setstatus semgrep nicht-installiert
|
||||||
|
report_tool semgrep
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
setstatus semgrep fehler
|
setstatus semgrep fehler
|
||||||
(
|
SEMGREP_CT="tessera-semgrep-$$"
|
||||||
cd "$WORK/src" &&
|
if docker create --name "$SEMGREP_CT" -w /src \
|
||||||
SEMGREP_SEND_METRICS=off SEMGREP_ENABLE_VERSION_CHECK=0 \
|
-e SEMGREP_SEND_METRICS=off -e SEMGREP_ENABLE_VERSION_CHECK=0 \
|
||||||
tmo 1500 "$SEMGREP_BIN" scan --config p/default --config p/typescript \
|
"$SEMGREP_IMAGE" semgrep scan --config p/default --config p/typescript \
|
||||||
--config p/nodejs --config p/secrets --config p/dockerfile \
|
--config p/nodejs --config p/secrets --config p/dockerfile \
|
||||||
--metrics=off --quiet --timeout 20 --json --output "$REPORT/semgrep.json" . >/dev/null 2>&1
|
--metrics=off --quiet --timeout 20 --json --output /tmp/semgrep.json . >/dev/null 2>&1 &&
|
||||||
)
|
docker cp "$WORK/src/." "$SEMGREP_CT:/src" >/dev/null 2>&1; then
|
||||||
|
tmo "$T_SEMGREP" docker start -a "$SEMGREP_CT" >/dev/null 2>&1
|
||||||
|
_rc=$?
|
||||||
|
if [ "$_rc" = 124 ] || [ "$_rc" = "$BUDGET_RC" ]; then
|
||||||
|
note_rc "$_rc" semgrep "$REPORT/semgrep.json"
|
||||||
|
else
|
||||||
|
docker cp "$SEMGREP_CT:/tmp/semgrep.json" "$REPORT/semgrep.json" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
docker rm -f "$SEMGREP_CT" >/dev/null 2>&1
|
||||||
|
SEMGREP_CT=""
|
||||||
|
report_tool semgrep
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,6 +486,7 @@ EOF
|
|||||||
scan_trivy_fs() {
|
scan_trivy_fs() {
|
||||||
if [ ! -x "$TRIVY_BIN" ]; then
|
if [ ! -x "$TRIVY_BIN" ]; then
|
||||||
setstatus trivy-fs nicht-installiert
|
setstatus trivy-fs nicht-installiert
|
||||||
|
report_tool trivy-fs
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
setstatus trivy-fs fehler
|
setstatus trivy-fs fehler
|
||||||
@@ -400,46 +494,58 @@ scan_trivy_fs() {
|
|||||||
mkdir -p "$TRIVY_CACHE" 2>/dev/null
|
mkdir -p "$TRIVY_CACHE" 2>/dev/null
|
||||||
(
|
(
|
||||||
cd "$WORK/src" &&
|
cd "$WORK/src" &&
|
||||||
tmo 900 "$TRIVY_BIN" fs --cache-dir "$TRIVY_CACHE" --quiet --no-progress --skip-version-check \
|
tmo "$T_TRIVY_FS" "$TRIVY_BIN" fs --cache-dir "$TRIVY_CACHE" --quiet --no-progress --skip-version-check \
|
||||||
--scanners vuln,misconfig,secret --secret-config "$WORK/trivy-secret.yaml" \
|
--scanners vuln,misconfig,secret --secret-config "$WORK/trivy-secret.yaml" \
|
||||||
--exit-code 0 --format json --output "$REPORT/trivy-fs.json" . >/dev/null 2>&1
|
--exit-code 0 --format json --output "$REPORT/trivy-fs.json" . >/dev/null 2>&1
|
||||||
)
|
)
|
||||||
|
note_rc $? trivy-fs "$REPORT/trivy-fs.json"
|
||||||
|
report_tool trivy-fs
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_trivy_images() {
|
scan_trivy_images() {
|
||||||
[ -n "$IMAGES" ] || return 0
|
if [ -z "$IMAGES" ]; then
|
||||||
|
emit "SECURITY-SUMMARY trivy-image skipped reason=kein-abbild-fuer-diesen-ref"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
for _img in $IMAGES; do
|
for _img in $IMAGES; do
|
||||||
_name="$(image_name "$_img")"
|
_name="$(image_name "$_img")"
|
||||||
|
_tool="trivy-image-$_name"
|
||||||
if [ ! -x "$TRIVY_BIN" ]; then
|
if [ ! -x "$TRIVY_BIN" ]; then
|
||||||
setstatus "trivy-image-$_name" nicht-installiert
|
setstatus "$_tool" nicht-installiert
|
||||||
|
report_tool "$_tool"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
if ! command -v docker >/dev/null 2>&1 || ! docker image inspect "$_img" >/dev/null 2>&1; then
|
if ! command -v docker >/dev/null 2>&1 || ! docker image inspect "$_img" >/dev/null 2>&1; then
|
||||||
setstatus "trivy-image-$_name" abbild-fehlt
|
setstatus "$_tool" abbild-fehlt
|
||||||
|
report_tool "$_tool"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
setstatus "trivy-image-$_name" fehler
|
setstatus "$_tool" fehler
|
||||||
mkdir -p "$TRIVY_CACHE" 2>/dev/null
|
mkdir -p "$TRIVY_CACHE" 2>/dev/null
|
||||||
tmo 900 "$TRIVY_BIN" image --image-src docker --cache-dir "$TRIVY_CACHE" --quiet --no-progress \
|
tmo "$T_TRIVY_IMAGE" "$TRIVY_BIN" image --image-src docker --cache-dir "$TRIVY_CACHE" --quiet --no-progress \
|
||||||
--skip-version-check --scanners vuln,secret --exit-code 0 --format json \
|
--skip-version-check --scanners vuln,secret --exit-code 0 --format json \
|
||||||
--output "$REPORT/trivy-image-$_name.json" "$_img" >/dev/null 2>&1
|
--output "$REPORT/$_tool.json" "$_img" >/dev/null 2>&1
|
||||||
|
note_rc $? "$_tool" "$REPORT/$_tool.json"
|
||||||
|
report_tool "$_tool"
|
||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Zusammenfassung: nur Zaehlwerte, geschrieben ins Protokoll und nach summary.txt.
|
# Zeile ins Protokoll UND (sobald der Berichtsordner steht) nach summary.txt.
|
||||||
summarize() {
|
emit() {
|
||||||
_imgnames=""
|
echo "$1"
|
||||||
for _img in $IMAGES; do
|
[ -n "$WORK" ] && [ -d "$REPORT" ] && echo "$1" >>"$REPORT/summary.txt"
|
||||||
_imgnames="$_imgnames $(image_name "$_img")"
|
return 0
|
||||||
done
|
}
|
||||||
if command -v python3 >/dev/null 2>&1; then
|
|
||||||
python3 -I -c '
|
# Auswertung EINES Werkzeugs (nur Zaehlwerte): Hilfsprogramm im Arbeitsordner, damit es
|
||||||
|
# nach jedem Werkzeug einzeln aufgerufen werden kann.
|
||||||
|
write_summary_helper() {
|
||||||
|
cat >"$WORK/summ.py" <<'EOF'
|
||||||
import json, os, sys
|
import json, os, sys
|
||||||
|
|
||||||
rep, st, names = sys.argv[1], sys.argv[2], sys.argv[3].split()
|
rep, st, tool = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
out = []
|
|
||||||
|
|
||||||
|
|
||||||
def load(name):
|
def load(name):
|
||||||
@@ -450,13 +556,16 @@ def load(name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def skip(tool):
|
def status():
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(st, tool), encoding="utf-8") as f:
|
with open(os.path.join(st, tool), encoding="utf-8") as f:
|
||||||
reason = f.read().split()[0]
|
return f.read().split()[0]
|
||||||
except Exception:
|
except Exception:
|
||||||
reason = "kein-bericht"
|
return "kein-bericht"
|
||||||
out.append("SECURITY-SUMMARY %s skipped reason=%s" % (tool, reason))
|
|
||||||
|
|
||||||
|
def skip():
|
||||||
|
return "SECURITY-SUMMARY %s skipped reason=%s" % (tool, status())
|
||||||
|
|
||||||
|
|
||||||
def sev_counts(results, key="Vulnerabilities"):
|
def sev_counts(results, key="Vulnerabilities"):
|
||||||
@@ -469,87 +578,80 @@ def sev_counts(results, key="Vulnerabilities"):
|
|||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
||||||
d = load("gitleaks.json")
|
line = None
|
||||||
if isinstance(d, list):
|
if tool == "gitleaks":
|
||||||
out.append("SECURITY-SUMMARY gitleaks findings=%d" % len(d))
|
d = load("gitleaks.json")
|
||||||
else:
|
if isinstance(d, list):
|
||||||
skip("gitleaks")
|
line = "SECURITY-SUMMARY gitleaks findings=%d" % len(d)
|
||||||
|
if status() == "flacher-klon":
|
||||||
|
line += " unvollstaendig (flacher Klon: nur die sichtbaren Commits geprueft)"
|
||||||
|
elif tool == "pnpm-audit-prod":
|
||||||
|
d = load("pnpm-audit-prod.json")
|
||||||
|
m = d.get("metadata", {}).get("vulnerabilities") if isinstance(d, dict) else None
|
||||||
|
if isinstance(m, dict):
|
||||||
|
line = (
|
||||||
|
"SECURITY-SUMMARY pnpm-audit-prod critical=%d high=%d moderate=%d low=%d"
|
||||||
|
% (m.get("critical", 0), m.get("high", 0), m.get("moderate", 0), m.get("low", 0))
|
||||||
|
)
|
||||||
|
elif tool == "osv-scanner":
|
||||||
|
d = load("osv-scanner.json")
|
||||||
|
if isinstance(d, dict) and isinstance(d.get("results"), list):
|
||||||
|
pk = set()
|
||||||
|
for r in d["results"]:
|
||||||
|
for p in r.get("packages") or []:
|
||||||
|
if p.get("vulnerabilities"):
|
||||||
|
x = p.get("package") or {}
|
||||||
|
pk.add((x.get("ecosystem"), x.get("name"), x.get("version")))
|
||||||
|
line = "SECURITY-SUMMARY osv-scanner packages=%d" % len(pk)
|
||||||
|
elif tool == "semgrep":
|
||||||
|
d = load("semgrep.json")
|
||||||
|
if isinstance(d, dict) and isinstance(d.get("results"), list):
|
||||||
|
line = "SECURITY-SUMMARY semgrep findings=%d errors=%d" % (
|
||||||
|
len(d["results"]),
|
||||||
|
len(d.get("errors") or []),
|
||||||
|
)
|
||||||
|
elif tool == "trivy-fs":
|
||||||
|
d = load("trivy-fs.json")
|
||||||
|
if isinstance(d, dict):
|
||||||
|
res = d.get("Results") or []
|
||||||
|
c = sev_counts(res)
|
||||||
|
mis = sum(len(r.get("Misconfigurations") or []) for r in res)
|
||||||
|
sec = sum(len(r.get("Secrets") or []) for r in res)
|
||||||
|
line = (
|
||||||
|
"SECURITY-SUMMARY trivy-fs critical=%d high=%d medium=%d low=%d misconfig=%d secrets=%d"
|
||||||
|
% (c["CRITICAL"], c["HIGH"], c["MEDIUM"], c["LOW"], mis, sec)
|
||||||
|
)
|
||||||
|
elif tool.startswith("trivy-image-"):
|
||||||
|
d = load(tool + ".json")
|
||||||
|
if isinstance(d, dict):
|
||||||
|
c = sev_counts(d.get("Results"))
|
||||||
|
line = "SECURITY-SUMMARY %s critical=%d high=%d medium=%d low=%d" % (
|
||||||
|
tool,
|
||||||
|
c["CRITICAL"],
|
||||||
|
c["HIGH"],
|
||||||
|
c["MEDIUM"],
|
||||||
|
c["LOW"],
|
||||||
|
)
|
||||||
|
print(line or skip())
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
d = load("pnpm-audit-prod.json")
|
report_tool() {
|
||||||
m = d.get("metadata", {}).get("vulnerabilities") if isinstance(d, dict) else None
|
if [ -n "$WORK" ] && [ -f "$WORK/summ.py" ] && command -v python3 >/dev/null 2>&1; then
|
||||||
if isinstance(m, dict):
|
_line="$(python3 -I "$WORK/summ.py" "$REPORT" "$WORK/status" "$1" 2>/dev/null)"
|
||||||
out.append(
|
[ -n "$_line" ] || _line="SECURITY-SUMMARY $1 skipped reason=kein-bericht"
|
||||||
"SECURITY-SUMMARY pnpm-audit-prod critical=%d high=%d moderate=%d low=%d"
|
emit "$_line"
|
||||||
% (m.get("critical", 0), m.get("high", 0), m.get("moderate", 0), m.get("low", 0))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
skip("pnpm-audit-prod")
|
|
||||||
|
|
||||||
d = load("osv-scanner.json")
|
|
||||||
if isinstance(d, dict) and isinstance(d.get("results"), list):
|
|
||||||
pk = set()
|
|
||||||
for r in d["results"]:
|
|
||||||
for p in r.get("packages") or []:
|
|
||||||
if p.get("vulnerabilities"):
|
|
||||||
x = p.get("package") or {}
|
|
||||||
pk.add((x.get("ecosystem"), x.get("name"), x.get("version")))
|
|
||||||
out.append("SECURITY-SUMMARY osv-scanner packages=%d" % len(pk))
|
|
||||||
else:
|
|
||||||
skip("osv-scanner")
|
|
||||||
|
|
||||||
d = load("semgrep.json")
|
|
||||||
if isinstance(d, dict) and isinstance(d.get("results"), list):
|
|
||||||
out.append(
|
|
||||||
"SECURITY-SUMMARY semgrep findings=%d errors=%d"
|
|
||||||
% (len(d["results"]), len(d.get("errors") or []))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
skip("semgrep")
|
|
||||||
|
|
||||||
d = load("trivy-fs.json")
|
|
||||||
if isinstance(d, dict):
|
|
||||||
res = d.get("Results") or []
|
|
||||||
c = sev_counts(res)
|
|
||||||
mis = sum(len(r.get("Misconfigurations") or []) for r in res)
|
|
||||||
sec = sum(len(r.get("Secrets") or []) for r in res)
|
|
||||||
out.append(
|
|
||||||
"SECURITY-SUMMARY trivy-fs critical=%d high=%d medium=%d low=%d misconfig=%d secrets=%d"
|
|
||||||
% (c["CRITICAL"], c["HIGH"], c["MEDIUM"], c["LOW"], mis, sec)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
skip("trivy-fs")
|
|
||||||
|
|
||||||
if names:
|
|
||||||
for n in names:
|
|
||||||
tool = "trivy-image-" + n
|
|
||||||
d = load(tool + ".json")
|
|
||||||
if isinstance(d, dict):
|
|
||||||
c = sev_counts(d.get("Results"))
|
|
||||||
out.append(
|
|
||||||
"SECURITY-SUMMARY %s critical=%d high=%d medium=%d low=%d"
|
|
||||||
% (tool, c["CRITICAL"], c["HIGH"], c["MEDIUM"], c["LOW"])
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
skip(tool)
|
|
||||||
else:
|
|
||||||
out.append("SECURITY-SUMMARY trivy-image skipped reason=kein-abbild-fuer-diesen-ref")
|
|
||||||
|
|
||||||
out.append("SECURITY-SUMMARY fertig (nur Bericht, Exit 0)")
|
|
||||||
text = "\n".join(out) + "\n"
|
|
||||||
try:
|
|
||||||
with open(os.path.join(rep, "summary.txt"), "w", encoding="utf-8") as f:
|
|
||||||
f.write(text)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
sys.stdout.write(text)
|
|
||||||
' "$REPORT" "$WORK/status" "$_imgnames" 2>/dev/null
|
|
||||||
else
|
else
|
||||||
skipline summary kein-python
|
emit "SECURITY-SUMMARY $1 skipped reason=kein-python"
|
||||||
echo "SECURITY-SUMMARY fertig (nur Bericht, Exit 0)"
|
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finish_line() {
|
||||||
|
emit "SECURITY-SUMMARY dauer=$(($(date +%s) - START_EPOCH))s von ${BUDGET_SECONDS}s"
|
||||||
|
emit "SECURITY-SUMMARY fertig (nur Bericht, Exit 0)"
|
||||||
|
}
|
||||||
|
|
||||||
do_run() {
|
do_run() {
|
||||||
if ! mkdir -p "$REPORT" 2>/dev/null; then
|
if ! mkdir -p "$REPORT" 2>/dev/null; then
|
||||||
skipline berichtsordner nicht-beschreibbar
|
skipline berichtsordner nicht-beschreibbar
|
||||||
@@ -567,6 +669,7 @@ do_run() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
mkdir -p "$WORK/src" "$WORK/status"
|
mkdir -p "$WORK/src" "$WORK/status"
|
||||||
|
write_summary_helper
|
||||||
|
|
||||||
# git darf in einem Ordner arbeiten, der einem anderen Benutzer gehoert (CI-Container
|
# git darf in einem Ordner arbeiten, der einem anderen Benutzer gehoert (CI-Container
|
||||||
# als root, lokaler Klon als Entwickler).
|
# als root, lokaler Klon als Entwickler).
|
||||||
@@ -586,14 +689,17 @@ do_run() {
|
|||||||
scan_semgrep
|
scan_semgrep
|
||||||
scan_trivy_fs
|
scan_trivy_fs
|
||||||
else
|
else
|
||||||
for _t in pnpm-audit-prod osv-scanner semgrep trivy-fs; do setstatus "$_t" export; done
|
for _t in pnpm-audit-prod osv-scanner semgrep trivy-fs; do
|
||||||
|
setstatus "$_t" export
|
||||||
|
report_tool "$_t"
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
scan_trivy_images
|
scan_trivy_images
|
||||||
|
|
||||||
# Layer-Zwischenspeicher von Trivy waechst stark; die Datenbank bleibt.
|
# Layer-Zwischenspeicher von Trivy waechst stark; die Datenbank bleibt.
|
||||||
rm -rf "$TRIVY_CACHE/fanal" 2>/dev/null
|
rm -rf "$TRIVY_CACHE/fanal" 2>/dev/null
|
||||||
|
|
||||||
summarize
|
finish_line
|
||||||
echo "Berichte: $REPORT (in der Pipeline als Artefakt sicherheitsberichte)"
|
echo "Berichte: $REPORT (in der Pipeline als Artefakt sicherheitsberichte)"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,11 @@
|
|||||||
# Entwicklungsrechner aus eine Anmeldung (401) verlangt. Der
|
# Entwicklungsrechner aus eine Anmeldung (401) verlangt. Der
|
||||||
# Kopf geht ueber eine temporaere Umgebungsdatei (Modus 0600,
|
# Kopf geht ueber eine temporaere Umgebungsdatei (Modus 0600,
|
||||||
# wird beim Beenden entfernt) in den Container, nie auf eine
|
# wird beim Beenden entfernt) in den Container, nie auf eine
|
||||||
# Befehlszeile und in keine Ausgabe.
|
# Befehlszeile und in keine Ausgabe. Der Haken schickt ihn NUR an
|
||||||
|
# das Ziel (ZAP_TARGET), nie an andere Adressen. Sonderzeichen im
|
||||||
|
# Passwort (" oder \) sind erlaubt.
|
||||||
|
#
|
||||||
|
# Berichtsordner: Modus 700 (nur der ZAP-Benutzer mit Kennung 1000 schreibt, niemand sonst liest).
|
||||||
#
|
#
|
||||||
# Hinweis: Wird die Anmeldedatei benutzt, enthaelt der Bericht die gesendeten Kopfzeilen
|
# Hinweis: Wird die Anmeldedatei benutzt, enthaelt der Bericht die gesendeten Kopfzeilen
|
||||||
# und damit die Zugangsdaten. Dann den Berichtsordner nicht weitergeben.
|
# und damit die Zugangsdaten. Dann den Berichtsordner nicht weitergeben.
|
||||||
@@ -64,10 +68,18 @@ if [ "${1:-}" = "--print-plan" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
TMP_FILES=""
|
TMP_FILES=""
|
||||||
|
RUN_LOG=""
|
||||||
|
# Entfernt die temporaeren Dateien mit dem Kopf (Zugangsdaten).
|
||||||
cleanup() {
|
cleanup() {
|
||||||
for f in $TMP_FILES; do rm -f "$f"; done
|
for f in $TMP_FILES; do rm -f "$f"; done
|
||||||
|
TMP_FILES=""
|
||||||
}
|
}
|
||||||
trap cleanup EXIT HUP INT TERM PIPE
|
# Beim Beenden (auch bei Abbruch) zusaetzlich das Zwischenprotokoll entfernen.
|
||||||
|
cleanup_all() {
|
||||||
|
cleanup
|
||||||
|
[ -n "$RUN_LOG" ] && rm -f "$RUN_LOG"
|
||||||
|
}
|
||||||
|
trap cleanup_all EXIT HUP INT TERM PIPE
|
||||||
|
|
||||||
echo "ZAP-Grundpruefung gegen $TARGET (nur passiv, keine Formulare, keine Angriffe)"
|
echo "ZAP-Grundpruefung gegen $TARGET (nur passiv, keine Formulare, keine Angriffe)"
|
||||||
|
|
||||||
@@ -84,19 +96,22 @@ case "$CODE" in
|
|||||||
echo "benutzer:passwort, und rufen Sie das Skript mit ZAP_BASIC_AUTH_FILE=Pfad auf." >&2
|
echo "benutzer:passwort, und rufen Sie das Skript mit ZAP_BASIC_AUTH_FILE=Pfad auf." >&2
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
CRED=$(head -n 1 "$ZAP_BASIC_AUTH_FILE" | tr -d '\r\n')
|
# Die Zugangsdaten gehen nur als base64-Text weiter (enthaelt keine Anfuehrungszeichen,
|
||||||
CURL_CFG=$(mktemp) || exit 4
|
# Rueckstriche oder Leerzeichen), also nie durch einen Parser, der Sonderzeichen deutet:
|
||||||
|
# weder curl-Konfiguration noch Befehlszeile. Passwoerter mit " oder \ sind unkritisch.
|
||||||
|
B64=$(head -n 1 "$ZAP_BASIC_AUTH_FILE" | tr -d '\r\n' | base64 | tr -d '\n')
|
||||||
|
HDR_FILE=$(mktemp) || exit 4
|
||||||
ENV_FILE=$(mktemp) || exit 4
|
ENV_FILE=$(mktemp) || exit 4
|
||||||
TMP_FILES="$CURL_CFG $ENV_FILE"
|
TMP_FILES="$HDR_FILE $ENV_FILE"
|
||||||
chmod 600 "$CURL_CFG" "$ENV_FILE"
|
chmod 600 "$HDR_FILE" "$ENV_FILE"
|
||||||
printf 'user = "%s"\n' "$CRED" > "$CURL_CFG"
|
printf 'Authorization: Basic %s\n' "$B64" > "$HDR_FILE"
|
||||||
CODE2=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 -K "$CURL_CFG" "$TARGET/login" 2>/dev/null) || CODE2=000
|
printf 'ZAP_BASIC_AUTH=Basic %s\n' "$B64" > "$ENV_FILE"
|
||||||
|
B64=""
|
||||||
|
CODE2=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 -H "@$HDR_FILE" "$TARGET/login" 2>/dev/null) || CODE2=000
|
||||||
if [ "$CODE2" != "200" ]; then
|
if [ "$CODE2" != "200" ]; then
|
||||||
echo "Mit den hinterlegten Zugangsdaten antwortet das Ziel mit Status $CODE2, Abbruch." >&2
|
echo "Mit den hinterlegten Zugangsdaten antwortet das Ziel mit Status $CODE2, Abbruch." >&2
|
||||||
exit 4
|
exit 4
|
||||||
fi
|
fi
|
||||||
printf 'ZAP_BASIC_AUTH=Basic %s\n' "$(printf '%s' "$CRED" | base64 | tr -d '\n')" > "$ENV_FILE"
|
|
||||||
CRED=""
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Das Ziel antwortet auf /login nicht mit Status 200 (Status $CODE), Abbruch." >&2
|
echo "Das Ziel antwortet auf /login nicht mit Status 200 (Status $CODE), Abbruch." >&2
|
||||||
@@ -106,9 +121,24 @@ case "$CODE" in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
# --- Berichtsordner: der Container laeuft als Benutzer zap (Kennung 1000) ---------
|
# --- Berichtsordner: der Container laeuft als Benutzer zap (Kennung 1000) ---------
|
||||||
|
# Nur dieser eine Benutzer darf schreiben (Modus 700), kein anderer Benutzer des Rechners
|
||||||
|
# darf lesen -- der Bericht kann die Zugangsdaten enthalten. Hat Ihr Konto die Kennung
|
||||||
|
# 1000 (der Normalfall), genuegt chmod; sonst uebergibt ein kurzer Container-Aufruf den
|
||||||
|
# Ordner an die Kennung 1000 und danach wieder an Sie zurueck.
|
||||||
mkdir -p "$REPORT_DIR" || exit 3
|
mkdir -p "$REPORT_DIR" || exit 3
|
||||||
REPORT_DIR=$(cd "$REPORT_DIR" && pwd)
|
REPORT_DIR=$(cd "$REPORT_DIR" && pwd)
|
||||||
chmod 777 "$REPORT_DIR"
|
chmod 700 "$REPORT_DIR"
|
||||||
|
ME_UID=$(id -u)
|
||||||
|
ME_GID=$(id -g)
|
||||||
|
HANDOVER=no
|
||||||
|
if [ "$ME_UID" != "1000" ]; then
|
||||||
|
if docker run --rm --user 0:0 -v "$REPORT_DIR:/zap/wrk:rw" "$IMAGE" chown 1000:1000 /zap/wrk >/dev/null 2>&1; then
|
||||||
|
HANDOVER=yes
|
||||||
|
else
|
||||||
|
echo "Der Berichtsordner konnte nicht an den ZAP-Benutzer (Kennung 1000) uebergeben werden." >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
set -- docker run --rm
|
set -- docker run --rm
|
||||||
[ -n "${ZAP_DOCKER_NETWORK:-}" ] && set -- "$@" --network "$ZAP_DOCKER_NETWORK"
|
[ -n "${ZAP_DOCKER_NETWORK:-}" ] && set -- "$@" --network "$ZAP_DOCKER_NETWORK"
|
||||||
@@ -119,10 +149,20 @@ set -- "$@" -v "$REPORT_DIR:/zap/wrk:rw" -v "$HOOK_FILE:$HOOK_IN_CONTAINER:ro" "
|
|||||||
|
|
||||||
# Exit-Codes von ZAP (0 ok, 1 Fehler, 2 Warnung, 3 Fehlstart) sind hier nicht
|
# Exit-Codes von ZAP (0 ok, 1 Fehler, 2 Warnung, 3 Fehlstart) sind hier nicht
|
||||||
# entscheidend: es zaehlt, ob der Bericht entstanden ist.
|
# entscheidend: es zaehlt, ob der Bericht entstanden ist.
|
||||||
"$@" > "$REPORT_DIR/zap-lauf.log" 2>&1
|
# Das Protokoll geht zuerst in eine eigene Datei: der Berichtsordner gehoert waehrend des
|
||||||
|
# Laufs eventuell der Kennung 1000, nicht Ihrem Konto.
|
||||||
|
RUN_LOG=$(mktemp) || exit 3
|
||||||
|
"$@" > "$RUN_LOG" 2>&1
|
||||||
# Die Umgebungsdatei mit dem Kopf wird nur fuer den Lauf gebraucht: sofort entfernen.
|
# Die Umgebungsdatei mit dem Kopf wird nur fuer den Lauf gebraucht: sofort entfernen.
|
||||||
cleanup
|
cleanup
|
||||||
TMP_FILES=""
|
if [ "$HANDOVER" = yes ]; then
|
||||||
|
# Ordner samt Inhalt zurueck an das eigene Konto, damit Sie den Bericht lesen koennen.
|
||||||
|
docker run --rm --user 0:0 -v "$REPORT_DIR:/zap/wrk:rw" "$IMAGE" chown -R "$ME_UID:$ME_GID" /zap/wrk >/dev/null 2>&1 ||
|
||||||
|
echo "Hinweis: Der Berichtsordner gehoert noch der Kennung 1000; mit sudo chown -R $ME_UID:$ME_GID uebernehmen." >&2
|
||||||
|
fi
|
||||||
|
cat "$RUN_LOG" > "$REPORT_DIR/zap-lauf.log" 2>/dev/null
|
||||||
|
rm -f "$RUN_LOG"
|
||||||
|
RUN_LOG=""
|
||||||
echo "ZAP-Lauf beendet (Protokoll: $REPORT_DIR/zap-lauf.log)"
|
echo "ZAP-Lauf beendet (Protokoll: $REPORT_DIR/zap-lauf.log)"
|
||||||
|
|
||||||
if [ ! -s "$REPORT_DIR/zap.json" ]; then
|
if [ ! -s "$REPORT_DIR/zap.json" ]; then
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ Aufgaben:
|
|||||||
die Pruefung rein passiv: es werden nur Seiten abgerufen (GET), nie Daten
|
die Pruefung rein passiv: es werden nur Seiten abgerufen (GET), nie Daten
|
||||||
an die Anwendung geschickt (kein POST).
|
an die Anwendung geschickt (kein POST).
|
||||||
2. Nur wenn die Umgebungsvariable ZAP_BASIC_AUTH gesetzt ist (Anmeldedatei-Weg des
|
2. Nur wenn die Umgebungsvariable ZAP_BASIC_AUTH gesetzt ist (Anmeldedatei-Weg des
|
||||||
Skripts), wird ein Ersetzungsregel fuer den Kopf Authorization eingetragen.
|
Skripts), wird eine Ersetzungsregel fuer den Kopf Authorization eingetragen --
|
||||||
|
beschraenkt auf Adressen des Ziels (Schema, Host, Port). Andere Hosts (Weiterleitungen,
|
||||||
|
eingebundene Fremdseiten, Zusatzdienste von ZAP) bekommen den Kopf nie.
|
||||||
Der Wert steht nie in einem Aufruf oder einer Ausgabe.
|
Der Wert steht nie in einem Aufruf oder einer Ausgabe.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
def zap_started(zap, target):
|
def zap_started(zap, target):
|
||||||
@@ -21,6 +24,10 @@ def zap_started(zap, target):
|
|||||||
|
|
||||||
auth = os.environ.get("ZAP_BASIC_AUTH")
|
auth = os.environ.get("ZAP_BASIC_AUTH")
|
||||||
if auth:
|
if auth:
|
||||||
|
# Die Regel gilt nur fuer URLs, die mit der Ziel-Adresse beginnen (Java-Regex, die
|
||||||
|
# ganze URL muss passen). Danach darf nur ein Pfad, eine Abfrage oder ein Anker
|
||||||
|
# folgen -- so passt auch http://host:30001 nicht auf das Ziel http://host:3000.
|
||||||
|
url_regex = re.escape(target.rstrip("/")) + r"(?:[/?#].*)?"
|
||||||
zap.replacer.add_rule(
|
zap.replacer.add_rule(
|
||||||
description="basicauth",
|
description="basicauth",
|
||||||
enabled=True,
|
enabled=True,
|
||||||
@@ -29,4 +36,5 @@ def zap_started(zap, target):
|
|||||||
matchregex=False,
|
matchregex=False,
|
||||||
replacement=auth,
|
replacement=auth,
|
||||||
initiators="",
|
initiators="",
|
||||||
|
url=url_regex,
|
||||||
)
|
)
|
||||||
|
|||||||
+15
-2
@@ -37,6 +37,11 @@ COPY apps/api/package.json ./apps/api/
|
|||||||
COPY packages/shared/package.json ./packages/shared/
|
COPY packages/shared/package.json ./packages/shared/
|
||||||
COPY apps/api/prisma ./apps/api/prisma/
|
COPY apps/api/prisma ./apps/api/prisma/
|
||||||
RUN pnpm install --frozen-lockfile --prod --filter=@tessera/api...
|
RUN pnpm install --frozen-lockfile --prod --filter=@tessera/api...
|
||||||
|
# Der Prisma-Client entsteht im postinstall von apps/api, der Fehler verschluckt
|
||||||
|
# (`prisma generate || true`). Deshalb hier noch einmal ausdruecklich erzeugen und pruefen:
|
||||||
|
# fehlt der Client, scheitert der Bau -- statt eines Abbilds, das erst beim Start stirbt.
|
||||||
|
RUN apps/api/node_modules/.bin/prisma generate --schema apps/api/prisma/schema.prisma \
|
||||||
|
&& find node_modules/.pnpm -path '*/.prisma/client/index.js' | grep -q .
|
||||||
|
|
||||||
# quick-261009-p0m: Laufzeitstufe direkt vom Node-Abbild (ohne das vorbereitete pnpm
|
# quick-261009-p0m: Laufzeitstufe direkt vom Node-Abbild (ohne das vorbereitete pnpm
|
||||||
# der Stufe base). Die Paketverwaltungen des Node-Abbilds (npm, npx, corepack, yarn)
|
# der Stufe base). Die Paketverwaltungen des Node-Abbilds (npm, npx, corepack, yarn)
|
||||||
@@ -49,8 +54,16 @@ ARG APP_CHANNEL
|
|||||||
ARG APP_COMMIT
|
ARG APP_COMMIT
|
||||||
ARG APP_BUILD_TIME
|
ARG APP_BUILD_TIME
|
||||||
ENV APP_VERSION=$APP_VERSION APP_CHANNEL=$APP_CHANNEL APP_COMMIT=$APP_COMMIT APP_BUILD_TIME=$APP_BUILD_TIME
|
ENV APP_VERSION=$APP_VERSION APP_CHANNEL=$APP_CHANNEL APP_COMMIT=$APP_COMMIT APP_BUILD_TIME=$APP_BUILD_TIME
|
||||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn-v1.22.22 \
|
# Die Ordner-/Dateinamen werden mit Platzhaltern gesucht (yarn-v1.22.22 wechselt mit dem
|
||||||
/usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn /usr/local/bin/yarnpkg
|
# Node-Abbild); die letzte Zeile laesst den Bau scheitern, falls eines der Werkzeuge
|
||||||
|
# bleibt -- sonst verpufft die Absicherung still bei einem neuen Basisabbild.
|
||||||
|
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn* \
|
||||||
|
/usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn* \
|
||||||
|
&& for c in npm npx corepack yarn yarnpkg; do \
|
||||||
|
if command -v "$c" >/dev/null 2>&1; then echo "FEHLER: $c ist noch im Abbild" >&2; exit 1; fi; \
|
||||||
|
done \
|
||||||
|
&& [ -z "$(ls -d /opt/yarn* /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack 2>/dev/null)" ] \
|
||||||
|
&& node --version
|
||||||
RUN addgroup --system --gid 1001 nestjs && \
|
RUN addgroup --system --gid 1001 nestjs && \
|
||||||
adduser --system --uid 1001 nestjs && \
|
adduser --system --uid 1001 nestjs && \
|
||||||
mkdir -p /app/user-files && \
|
mkdir -p /app/user-files && \
|
||||||
|
|||||||
+10
-2
@@ -44,8 +44,16 @@ ARG APP_BUILD_TIME
|
|||||||
ENV APP_VERSION=$APP_VERSION APP_CHANNEL=$APP_CHANNEL APP_COMMIT=$APP_COMMIT APP_BUILD_TIME=$APP_BUILD_TIME
|
ENV APP_VERSION=$APP_VERSION APP_CHANNEL=$APP_CHANNEL APP_COMMIT=$APP_COMMIT APP_BUILD_TIME=$APP_BUILD_TIME
|
||||||
# quick-261009-p0m: Paketverwaltungen des Node-Abbilds (npm, npx, corepack, yarn) entfernen;
|
# quick-261009-p0m: Paketverwaltungen des Node-Abbilds (npm, npx, corepack, yarn) entfernen;
|
||||||
# der Web-Server braucht nur `node`.
|
# der Web-Server braucht nur `node`.
|
||||||
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn-v1.22.22 \
|
# Die Ordner-/Dateinamen werden mit Platzhaltern gesucht (yarn-v1.22.22 wechselt mit dem
|
||||||
/usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn /usr/local/bin/yarnpkg
|
# Node-Abbild); die letzte Zeile laesst den Bau scheitern, falls eines der Werkzeuge
|
||||||
|
# bleibt -- sonst verpufft die Absicherung still bei einem neuen Basisabbild.
|
||||||
|
RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack /opt/yarn* \
|
||||||
|
/usr/local/bin/npm /usr/local/bin/npx /usr/local/bin/corepack /usr/local/bin/yarn* \
|
||||||
|
&& for c in npm npx corepack yarn yarnpkg; do \
|
||||||
|
if command -v "$c" >/dev/null 2>&1; then echo "FEHLER: $c ist noch im Abbild" >&2; exit 1; fi; \
|
||||||
|
done \
|
||||||
|
&& [ -z "$(ls -d /opt/yarn* /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/corepack 2>/dev/null)" ] \
|
||||||
|
&& node --version
|
||||||
RUN addgroup --system --gid 1001 nodejs && \
|
RUN addgroup --system --gid 1001 nodejs && \
|
||||||
adduser --system --uid 1001 nextjs
|
adduser --system --uid 1001 nextjs
|
||||||
COPY --from=builder /app/apps/web/public ./apps/web/public
|
COPY --from=builder /app/apps/web/public ./apps/web/public
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ function readChangelog(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schutz-Kopfzeilen der Anwendung (quick-261009-p0m, aus dem ersten ZAP-Lauf). Sie gelten fuer
|
// Schutz-Kopfzeilen der Anwendung (quick-261009-p0m, aus dem ersten ZAP-Lauf). Sie gelten fuer
|
||||||
// alle Pfade und stehen hier, damit sie auch ohne vorgeschalteten Proxy gesendet werden
|
// alle Pfade und stehen hier, damit sie auch ohne vorgeschalteten Proxy gesendet werden.
|
||||||
// (doppelt gesichert; setzt der Proxy dieselben Kopfzeilen, gewinnt keine Seite etwas oder
|
// Setzt der Proxy dieselben Kopfzeilen mit gleichem Wert, aendert sich nichts. Setzt er sie mit
|
||||||
// verliert etwas). Absichtlich KEINE vollstaendige Content-Security-Policy -- sie braucht eine
|
// ANDEREM Wert (etwa eine eigene Content-Security-Policy oder X-Frame-Options: DENY), gelten
|
||||||
|
// beide nebeneinander und der Browser nimmt die strengere -- das kann Tessera-Seiten sperren,
|
||||||
|
// die sich selbst einrahmen. Ob der Proxy welche sendet, ist mit dem ersten ZAP-Lauf gegen die
|
||||||
|
// oeffentliche Adresse zu pruefen (docs/sicherheitsprotokoll.md).
|
||||||
|
// Absichtlich KEINE vollstaendige Content-Security-Policy -- sie braucht eine
|
||||||
// eigene Pruefung der Inline-Skripte von Next.js und bleibt im Sicherheitsprotokoll "offen".
|
// eigene Pruefung der Inline-Skripte von Next.js und bleibt im Sicherheitsprotokoll "offen".
|
||||||
// `frame-ancestors 'self'` ist nur die eine Richtlinie gegen das Einrahmen durch fremde Seiten
|
// `frame-ancestors 'self'` ist nur die eine Richtlinie gegen das Einrahmen durch fremde Seiten
|
||||||
// (Gegenstueck zu X-Frame-Options fuer neuere Browser). Eingebettete FREMDE Seiten (XFrame-Kachel,
|
// (Gegenstueck zu X-Frame-Options fuer neuere Browser). Eingebettete FREMDE Seiten (XFrame-Kachel,
|
||||||
|
|||||||
Reference in New Issue
Block a user