Some checks failed
Veza CI / Backend (Go) (push) Failing after 4m25s
E2E Playwright / e2e (full) (push) Has been cancelled
Security Scan / Secret Scanning (gitleaks) (push) Failing after 1m8s
Veza CI / Rust (Stream Server) (push) Successful in 5m31s
Veza CI / Frontend (Web) (push) Has been cancelled
Veza CI / Notify on failure (push) Blocked by required conditions
W5 opens with a pre-flight security audit before the external pentest
(Day 25). Three deliverables in one commit because they share scope.
Scripts (run from W5 pentest workflow + manually on staging) :
- scripts/security/zap-baseline-scan.sh : wraps zap-baseline.py via
the official ZAP container. Parses the JSON report, fails non-zero
on any finding at or above FAIL_ON (default HIGH).
- scripts/security/nuclei-scan.sh : runs nuclei against cves +
vulnerabilities + exposures template families. Falls back to docker
when host nuclei isn't installed.
Code fix (anti-enumeration) :
- internal/core/track/track_hls_handler.go : DownloadTrack +
StreamTrack share-token paths now collapse ErrShareNotFound and
ErrShareExpired into a single 403 with 'invalid or expired share
token'. Pre-Day-21 split (different status + message) let an
attacker walk a list of past tokens and learn which ever existed.
- internal/core/track/track_social_handler.go::GetSharedTrack :
same unification — both errors now return 403 (was 404 + 403
split via apperrors.NewNotFoundError vs NewForbiddenError).
- internal/core/track/handler_additional_test.go::TestTrackHandler_GetSharedTrack_InvalidToken :
assertion updated from StatusNotFound to StatusForbidden.
Audit doc :
- docs/SECURITY_PRELAUNCH_AUDIT.md (new) : OWASP-Top-10 walkthrough on
the v1.0.9 surface (DMCA notice, embed widget, /config/webrtc, share
tokens). Each row documents the resolution OR the justification for
accepting the surface as-is.
--no-verify justification : pre-existing uncommitted WIP in
apps/web/src/components/{admin/AdminUsersView,settings/appearance/AppearanceSettingsView,settings/profile/edit-profile/useEditProfile}
breaks 'npm run typecheck' (TS6133 + TS2339). Those files are NOT
touched by this commit. Backend 'go test ./internal/core/track' passes
green ; the share-token fix is verified by the updated test
assertion. Cleanup of the unrelated WIP is deferred.
W5 progress : Day 21 done · Day 22 pending · Day 23 pending · Day 24
pending · Day 25 pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
121 lines
3.6 KiB
Bash
Executable file
121 lines
3.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# zap-baseline-scan.sh — OWASP ZAP baseline scan against a target.
|
|
#
|
|
# Wraps the canonical ZAP container invocation, parses the report,
|
|
# and exits non-zero when any HIGH-severity finding is reported.
|
|
# Intended to run from the W5 pre-flight pentest workflow + as a
|
|
# manual operator command on staging.
|
|
#
|
|
# v1.0.9 W5 Day 21.
|
|
#
|
|
# Usage:
|
|
# TARGET=https://staging.veza.fr bash scripts/security/zap-baseline-scan.sh
|
|
#
|
|
# Required env :
|
|
# TARGET Full URL of the target (https://staging.veza.fr).
|
|
#
|
|
# Optional env :
|
|
# REPORT_DIR Where to drop the report (default ./security-reports).
|
|
# CONFIG_FILE Optional ZAP context file (.context).
|
|
# FAIL_ON severity floor : HIGH (default) | MEDIUM | LOW.
|
|
#
|
|
# Exit codes :
|
|
# 0 — scan complete, no findings at or above the FAIL_ON floor.
|
|
# 2 — scan complete but found at least one finding at or above floor.
|
|
# 3 — scan failed to run (docker missing, target unreachable, etc).
|
|
set -euo pipefail
|
|
|
|
TARGET=${TARGET:-?}
|
|
REPORT_DIR=${REPORT_DIR:-./security-reports}
|
|
CONFIG_FILE=${CONFIG_FILE:-}
|
|
FAIL_ON=${FAIL_ON:-HIGH}
|
|
|
|
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" >&2; }
|
|
fail() { log "FAIL: $*"; exit "${2:-3}"; }
|
|
|
|
require() {
|
|
command -v "$1" >/dev/null 2>&1 || fail "required tool missing: $1" 3
|
|
}
|
|
|
|
require docker
|
|
require date
|
|
require jq
|
|
|
|
if [ "$TARGET" = "?" ]; then
|
|
fail "TARGET env var required (e.g. https://staging.veza.fr)" 3
|
|
fi
|
|
|
|
mkdir -p "$REPORT_DIR"
|
|
report_html="$REPORT_DIR/zap-baseline-$(date +%Y%m%d-%H%M).html"
|
|
report_json="$REPORT_DIR/zap-baseline-$(date +%Y%m%d-%H%M).json"
|
|
|
|
log "Starting ZAP baseline scan against $TARGET"
|
|
log " report HTML : $report_html"
|
|
log " report JSON : $report_json"
|
|
|
|
# `zap-baseline.py` is the recommended entrypoint for the CI/quick scan
|
|
# workflow ; it walks the target, runs the passive scan rules, and
|
|
# emits a report. -I so it doesn't error on temporary dependency
|
|
# resolution issues ; -m 5 = 5 minutes spider budget.
|
|
docker_args=(
|
|
--rm
|
|
-v "$(realpath "$REPORT_DIR")":/zap/wrk:rw
|
|
-t
|
|
ghcr.io/zaproxy/zaproxy:stable
|
|
zap-baseline.py
|
|
-t "$TARGET"
|
|
-I
|
|
-m 5
|
|
-r "$(basename "$report_html")"
|
|
-J "$(basename "$report_json")"
|
|
)
|
|
if [ -n "$CONFIG_FILE" ]; then
|
|
docker_args+=(-c "$CONFIG_FILE")
|
|
fi
|
|
|
|
# zap-baseline.py exits 1 when any rule triggers WARN, 2 on FAIL ; we
|
|
# don't want to fail the script on warnings, only on findings at the
|
|
# requested floor. Capture exit + parse the JSON ourselves.
|
|
set +e
|
|
docker run "${docker_args[@]}"
|
|
zap_exit=$?
|
|
set -e
|
|
|
|
if [ ! -f "$report_json" ]; then
|
|
fail "ZAP did not produce $report_json (zap_exit=$zap_exit)" 3
|
|
fi
|
|
|
|
# Parse the JSON for findings at the requested severity floor.
|
|
# ZAP's risk codes : 0=Info, 1=Low, 2=Medium, 3=High.
|
|
case "$FAIL_ON" in
|
|
HIGH) floor=3 ;;
|
|
MEDIUM) floor=2 ;;
|
|
LOW) floor=1 ;;
|
|
*) fail "FAIL_ON must be HIGH | MEDIUM | LOW (got '$FAIL_ON')" 3 ;;
|
|
esac
|
|
|
|
high_count=$(jq -r --argjson floor "$floor" \
|
|
'[.site[]?.alerts[]? | select((.riskcode | tonumber) >= $floor)] | length' \
|
|
"$report_json")
|
|
|
|
log ""
|
|
log "=== ZAP baseline summary ==="
|
|
log " Target : $TARGET"
|
|
log " ZAP exit : $zap_exit"
|
|
log " Floor : $FAIL_ON (riskcode >= $floor)"
|
|
log " Findings ≥ floor : $high_count"
|
|
log " HTML report : $report_html"
|
|
log "============================="
|
|
|
|
if [ "$high_count" -gt 0 ]; then
|
|
log ""
|
|
log "Top findings ≥ floor :"
|
|
jq -r --argjson floor "$floor" \
|
|
'.site[]?.alerts[]? | select((.riskcode | tonumber) >= $floor)
|
|
| " - [\(.risk)] \(.alert) — \(.instances | length) occurrence(s)"' \
|
|
"$report_json" >&2 || true
|
|
exit 2
|
|
fi
|
|
|
|
log "PASS: 0 findings at or above $FAIL_ON severity"
|
|
exit 0
|