#!/usr/bin/env bash # # build-baseline.sh — reproducibly assemble the www.archline.hu clean CODE baseline. # # Output (out/) = the deployed docroot (code only), which the operator's HIDS # baseline-diff verifies the live server against. # # The repo stores the baseline CONSTITUENTS, not the deployed tree; this script # performs the deterministic "deployment" (Joomla manifest-mapping for packaged # extensions, direct placement for no-source ones), per RUNBOOK.md. # # Usage: ./build-baseline.sh [OUT_DIR] (default: ./out) set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" OUT="${1:-$ROOT/out}" log(){ printf '[build] %s\n' "$*"; } rm -rf "$OUT"; mkdir -p "$OUT" # Ensure the developer-controlled submodules are populated (cadcommonlib, maintenance). git -C "$ROOT" submodule update --init \ cadline/backend/common_cadline_libraries cadline/backend/maintenance >/dev/null 2>&1 || \ log "WARN: submodule update failed — common_cadline_libraries/maintenance may be empty" # 1) Joomla core — official 3.8.11 package (deployed form), minus demo assets and # runtime data dirs (cache/tmp/logs/images are data, not verified code). log "core: Joomla 3.8.11 (official package)" rsync -a \ --exclude='/cache/' --exclude='/tmp/' --exclude='/logs/' \ --exclude='/images/sampledata/' --exclude='/images/banners/' --exclude='/images/headers/' \ --exclude='/images/joomla_black.png' --exclude='/images/powered_by.png' \ "$ROOT/core/" "$OUT/" # 2) Upstream-package extensions — deployed-layout, byte-reconciled to live. # In git these are authored as two commits per modified module: (a) the # pristine upstream package manifest-mapped by lib/jmap.py, (b) the reconcile # to the live bytes (line-endings + install files + Cadline delta). `git diff` # between those two commits documents exactly how live differs from upstream. # At build time the reconciled (live-byte) deployed files are placed directly. for d in "$ROOT"/packages/*/; do [ -d "$d" ] || continue log "package: $(basename "$d")" rsync -a --exclude='.provenance' "$d" "$OUT/" done # 3) No-source extensions — vetted deployed files (no obtainable package), # placed directly (already in deployed layout). for d in "$ROOT"/deployed/*/; do [ -d "$d" ] || continue log "deployed: $(basename "$d")" rsync -a --exclude='.provenance' "$d" "$OUT/" done # 4) Cadline own-code (components/modules/plugins/templates/docroot apps). for d in "$ROOT"/cadline/*/; do [ -d "$d" ] || continue log "cadline: $(basename "$d")" rsync -a --exclude='.provenance' --exclude='.git' "$d" "$OUT/" done # 5) Cadline core modifications — live versions overlaid on the package core # (the ~24 legit customizations); the 3 trojanized core files stay clean. if [ -d "$ROOT/overrides" ]; then log "overrides: Cadline core modifications" rsync -a --exclude='.gitkeep' "$ROOT/overrides/" "$OUT/" fi # NOTE: common_cadline_libraries + maintenance come from the authoritative # developer submodules (cadcommonlib, maintenance) as-is. Their content currently # diverges from the live docroot (mixed line-endings + a content drift on # crm_hardlock.class.php / POfile.php) because live was not last deployed from the # submodule HEADs. The baseline reflects the authoritative repos; the HIDS flags # the live divergence until live is re-deployed or the drift is reconciled. # Line-ending normalization is intentionally left to the verifier, not applied here. # 6) Safety net — CONTENT-based malware exclusion. The constituent sources above # are vetted clean, but this catches a planted file regardless of its extension # disguise. The 2026 incident planted one webshell under ~20 fake extensions # (.inc.json / .phar.json / .php[3-5].json / .php.json.json) plus plain-.json # defacements — an extension allowlist (the old '*.php.json' filter) missed ALL # of them. The robust signal is CONTENT, not extension. log "exclude: content-based malware safety net" # (a) Any .json under templates/ carrying a PHP open tag is NOT a legit Helix3 # layout preset (those are pure JSON) — it is a disguised dropper. This closes # the .json-disguise gap deterministically. if [ -d "$OUT/templates" ]; then find "$OUT/templates" -type f -name '*.json' 2>/dev/null | while IFS= read -r j; do if grep -Ilq -m1 -E '<\?php|<\?=' "$j" 2>/dev/null; then log " drop (php-in-json): ${j#"$OUT"/}"; rm -f "$j" fi done fi # (b) Legacy extension patterns (belt-and-suspenders; harmless if already gone). find "$OUT" \( -name '*.php.json' -o -name '*.phtml.json' -o -name 'h3x_*' -o -name '_h3x_*' \) -delete 2>/dev/null || true # (c) Explicit IOC path list (docroot-relative) — the plain-.json defacements, # probe artifacts and non-.json droppers that a content grep cannot key on. if [ -f "$ROOT/malware-iocs.paths" ]; then while IFS= read -r ioc; do [ -z "$ioc" ] && continue case "$ioc" in \#*) continue;; esac rm -rf "${OUT:?}/$ioc" done < "$ROOT/malware-iocs.paths" fi log "baseline built at: $OUT ($(find "$OUT" -type f | wc -l) files)"