Skip to content

deps(go): bump github.com/prometheus/common from 0.68.1 to 0.69.0 in the go-minor-patch group across 1 directory #1841

deps(go): bump github.com/prometheus/common from 0.68.1 to 0.69.0 in the go-minor-patch group across 1 directory

deps(go): bump github.com/prometheus/common from 0.68.1 to 0.69.0 in the go-minor-patch group across 1 directory #1841

# =============================================================================
# NFTBan - CI: Architecture Policy
# =============================================================================
# SPDX-License-Identifier: MPL-2.0
# Purpose: Enforce architecture invariants and code policy gates
#
# Checks:
# - nft write policy (single-writer via daemon)
# - Netlink import boundary (CLI must use IPC)
# - Public API surface guard (only pkg/ipc + pkg/version)
# - Suppression comment audit (//lint:ignore only, no //nolint:)
# - Import boundary guard (CLI cannot import nftbackend)
# - FHS spec generated files match committed versions
# - Distro config completeness (all .conf files valid)
# - Hardcoded path detector (paths should come from distro config)
# - systemd directive version guard (min systemd 249)
# - nft template placeholder guard (rendered config must be boot-safe)
# - Anchor jump placement lint G1-G3 (v1.64.0)
# - Anchor marker count validation (v1.64.0)
# - Bash hazard lint H1-H2 (v1.66.3): nft -a flag + set -e traps
# - Update safety lint U1-U2 (v1.70.0): rebuild failure must be fatal
# - Module isolation lint R-10 (v1.110.0): distinct ModuleName + EventBan
# source-label + Status.Extra cross-module key isolation (with baseline)
# =============================================================================
name: Architecture Policy
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master]
concurrency:
group: ci-arch-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
architecture-check:
name: Policy Gates
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
# =====================================================================
# EXISTING CHECKS
# =====================================================================
- name: Check nft write policy
run: ./scripts/ci/check-nft-writes.sh
env:
# Explicit: NEVER use --warn-all in CI (would disable enforcement)
WARN_ALL: "0"
# v1.192 transition-atomicity class (D-NFTBAN-SOAK-...-DROP-WINDOW + F-FEED/F-GEO):
# forbid flush/delete-then-repopulate in a separate transaction on the live
# nftban table (V-NFT-REBUILD-ATOMICITY) or shared sets (V-NFT-SET-REFRESH-ATOMICITY).
- name: Check nft transition atomicity
run: ./scripts/ci/check-nft-atomicity.sh
- name: Check netlink import policy
run: |
echo "=== Checking netlink import architecture policy ==="
VIOLATIONS=0
echo "Checking cmd/nftban-core for forbidden netlink imports..."
if grep -r "github.com/google/nftables" cmd/nftban-core/ 2>/dev/null; then
echo "::error::cmd/nftban-core imports netlink directly — CLI must use IPC (pkg/ipc)"
VIOLATIONS=1
else
echo "✓ cmd/nftban-core: No direct netlink imports"
fi
echo ""
echo "Verifying daemon uses netlink (expected)..."
if grep -q "github.com/google/nftables" cmd/nftband/main.go 2>/dev/null || \
grep -q "nftbackend" cmd/nftband/main.go 2>/dev/null; then
echo "✓ cmd/nftband: Uses nftbackend (correct — daemon owns nft writes)"
fi
if [[ $VIOLATIONS -gt 0 ]]; then
exit 1
fi
echo "✓ Netlink import policy: PASSED"
- name: Public API surface guard
run: |
echo "=== Checking public API boundary (pkg/) ==="
VIOLATIONS=0
for gofile in $(find pkg/ -name "*.go" -type f 2>/dev/null); do
dir=$(dirname "$gofile")
case "$dir" in
pkg/ipc|pkg/ipc/*|pkg/version|pkg/version/*) ;;
*)
echo "::error::$gofile is outside allowed pkg/ directories — move to internal/"
VIOLATIONS=1
;;
esac
done
if [[ $VIOLATIONS -gt 0 ]]; then
exit 1
fi
echo "✓ Public API surface: PASS (only pkg/ipc and pkg/version)"
- name: Suppression comment audit
run: |
echo "=== Auditing suppression comments ==="
NOSEC_COUNT=$(grep -r '#nosec' --include="*.go" . 2>/dev/null | wc -l || echo 0)
NOLINT_COUNT=$(grep -r '//nolint:' --include="*.go" . 2>/dev/null | wc -l || echo 0)
LINT_IGNORE_COUNT=$(grep -r '//lint:ignore' --include="*.go" . 2>/dev/null | wc -l || echo 0)
echo " #nosec: $NOSEC_COUNT"
echo " //nolint:: $NOLINT_COUNT"
echo " //lint:ignore: $LINT_IGNORE_COUNT"
echo ""
if [[ "$NOLINT_COUNT" -gt 0 ]]; then
echo "::error::Found $NOLINT_COUNT //nolint: directives — use //lint:ignore <CHECK> <reason>"
grep -rn '//nolint:' --include="*.go" . 2>/dev/null || true
exit 1
fi
echo "✓ Suppression audit: PASS"
- name: Import boundary guard
run: |
echo "=== Checking import boundaries ==="
if grep -r 'internal/nftbackend' cmd/nftban-core/ --include="*.go" 2>/dev/null; then
echo "::error::cmd/nftban-core/ imports internal/nftbackend — CLI must use pkg/ipc"
exit 1
fi
echo "✓ Import boundary: PASS (CLI does not import nftbackend)"
- name: FHS spec generated files check
run: |
echo "=== Verifying FHS spec generated files are up to date ==="
if ! ./build/generate-fhs-outputs.sh --check; then
echo ""
echo "::error::Generated FHS files are out of date — run: ./build/generate-fhs-outputs.sh"
exit 1
fi
echo "✓ FHS spec: PASS (generated files match YAML spec)"
- name: Systemd install-list generated file check
run: |
echo "=== Verifying systemd install-list is up to date ==="
if ! ./build/generate-systemd-install-list.sh --check; then
echo ""
echo "::error::install/packaging/systemd/nftban-systemd-install.list is stale — run: ./build/generate-systemd-install-list.sh"
exit 1
fi
echo "✓ Systemd install-list: PASS (matches install/systemd source)"
- name: docs/systemd/UNITS.md count parity
run: |
echo "=== Verifying docs/systemd/UNITS.md counts match install list ==="
list=install/packaging/systemd/nftban-systemd-install.list
fs_timer=$(grep -cE '\.timer$' "$list")
fs_svc=$(grep -cE '\.service$' "$list")
fs_socket=$(grep -cE '\.socket$' "$list")
doc_timer=$(grep -oE '^## Timers \([0-9]+\)' docs/systemd/UNITS.md | grep -oE '[0-9]+' || echo 0)
doc_svc=$(grep -oE '^## Services \([0-9]+\)' docs/systemd/UNITS.md | grep -oE '[0-9]+' || echo 0)
doc_socket=$(grep -oE '^## Sockets \([0-9]+\)' docs/systemd/UNITS.md | grep -oE '[0-9]+' || echo 0)
fail=0
[ "$fs_timer" = "$doc_timer" ] || { echo "::error::timer count drift: list=$fs_timer doc=$doc_timer"; fail=1; }
[ "$fs_svc" = "$doc_svc" ] || { echo "::error::service count drift: list=$fs_svc doc=$doc_svc"; fail=1; }
[ "$fs_socket" = "$doc_socket" ] || { echo "::error::socket count drift: list=$fs_socket doc=$doc_socket"; fail=1; }
[ "$fail" = "0" ] || exit 1
echo "✓ docs/systemd/UNITS.md counts: PASS ($fs_timer timers, $fs_svc services, $fs_socket socket)"
- name: Systemd maintainer-script generated files check
run: |
echo "=== Verifying systemd maintainer-script artifacts are up to date ==="
if ! ./build/generate-systemd-maintainer-scripts.sh --check; then
echo ""
echo "::error::Systemd maintainer-script artifacts are stale — run: ./build/generate-systemd-maintainer-scripts.sh"
exit 1
fi
echo "✓ Systemd maintainer-scripts: PASS (matches generator output)"
- name: C3 maintainer-script structural assertions (A5)
run: |
echo "=== A5: structural assertions on generated cleanup artifacts ==="
rpm_art=install/packaging/rpm/nftban-preun-systemd-cleanup.inc
deb_art=install/packaging/deb/nftban-prerm-systemd-cleanup.inc
fail=0
# A5.1 — both artifacts contain mask_if_exists helper
grep -qE '^mask_if_exists\(\)' "$rpm_art" || { echo "::error::RPM artifact missing mask_if_exists() helper"; fail=1; }
grep -qE '^mask_if_exists\(\)' "$deb_art" || { echo "::error::DEB artifact missing mask_if_exists() helper"; fail=1; }
# A5.2 — both artifacts have the readlink /dev/null boundary check
grep -qE 'readlink "/etc/systemd/system/\$unit".*= *"/dev/null"' "$rpm_art" || { echo "::error::RPM artifact missing readlink /dev/null check (A1 policy)"; fail=1; }
grep -qE 'readlink "/etc/systemd/system/\$unit".*= *"/dev/null"' "$deb_art" || { echo "::error::DEB artifact missing readlink /dev/null check (A1 policy)"; fail=1; }
# A5.3 — no unconditional `systemctl mask nftban-ui*` literal outside mask_if_exists in any maintainer-script source
if grep -nE 'systemctl mask "[^"]*nftban-ui[^"]*"' packaging/build_nftban.sh packaging/deb/prerm 2>/dev/null \
| grep -v 'mask_if_exists'; then
echo "::error::unconditional systemctl mask nftban-ui* found outside mask_if_exists (A1 regression)"
fail=1
fi
[ "$fail" = "0" ] || exit 1
echo "✓ C3 structural assertions: PASS (mask_if_exists + /dev/null boundary + no unconditional mask)"
# =====================================================================
# MFST-C6 (v1.107): packaging-wire structural assertions (AM-4)
# =====================================================================
# Proves both packagers consume generated manifest authority artifacts.
# If a future edit silently replaces a generator-driven %include / while-read
# loop with hardcoded content, these greps fail before the build runs.
# AM-4.1: RPM Source1 + %include of nftban-files.inc
# AM-4.2: DEB build_deb() while-read of nftban.dirs
# AM-4.3: RPM/DEB consume nftban-systemd-install.list
- name: MFST-C6 packaging-wire structural assertions (AM-4)
run: |
echo "=== MFST-C6 / AM-4: packaging-wire structural assertions ==="
spec=packaging/build_nftban.sh
fail=0
# AM-4.1 — RPM consumes generated nftban-files.inc
grep -qE '^Source1:[[:space:]]+nftban-files\.inc' "$spec" \
|| { echo "::error::AM-4.1: RPM Source1: nftban-files.inc missing in $spec"; fail=1; }
grep -qE '^%include[[:space:]]+%\{_sourcedir\}/nftban-files\.inc' "$spec" \
|| { echo "::error::AM-4.1: %include %{_sourcedir}/nftban-files.inc missing in $spec"; fail=1; }
# AM-4.2 — DEB build_deb() consumes generated nftban.dirs via while-read
if ! awk '/^build_deb\(\)/,/^\}/' "$spec" \
| grep -qE 'install/packaging/deb/nftban\.dirs'; then
echo "::error::AM-4.2: build_deb() does not reference install/packaging/deb/nftban.dirs in $spec"
fail=1
fi
if ! awk '/^build_deb\(\)/,/^\}/' "$spec" \
| grep -qE 'while[[:space:]]+(IFS=.*[[:space:]]+)?read'; then
echo "::error::AM-4.2: build_deb() lacks while-read loop consuming nftban.dirs in $spec"
fail=1
fi
# AM-4.3 — RPM and DEB both consume generated nftban-systemd-install.list
grep -qE '^Source2:[[:space:]]+nftban-systemd-install\.list' "$spec" \
|| { echo "::error::AM-4.3: RPM Source2: nftban-systemd-install.list missing in $spec"; fail=1; }
# %install RPM body must reference the generated install list
grep -qE '%\{_sourcedir\}/nftban-systemd-install\.list' "$spec" \
|| { echo "::error::AM-4.3: RPM %install does not reference %{_sourcedir}/nftban-systemd-install.list in $spec"; fail=1; }
# build_deb() must reference the generated install list
if ! awk '/^build_deb\(\)/,/^\}/' "$spec" \
| grep -qE 'install/packaging/systemd/nftban-systemd-install\.list'; then
echo "::error::AM-4.3: build_deb() does not reference install/packaging/systemd/nftban-systemd-install.list in $spec"
fail=1
fi
[ "$fail" = "0" ] || exit 1
echo "✓ MFST-C6 / AM-4: PASS (RPM Source1+%include + DEB while-read + RPM/DEB systemd-install-list)"
# =====================================================================
# MFST-C6 (v1.107): UNITS.md per-row parity (E6)
# =====================================================================
# Closes content-gap left by the existing count-only check above.
# Asserts every active unit listed in nftban-systemd-install.list (the C1
# source-of-truth) appears as a backticked table row in UNITS.md.
- name: MFST-C6 docs/systemd/UNITS.md table-row parity (E6)
run: |
echo "=== MFST-C6 / E6: UNITS.md per-row parity vs systemd install list ==="
list=install/packaging/systemd/nftban-systemd-install.list
doc=docs/systemd/UNITS.md
fail=0
missing=0
while IFS= read -r unit; do
# Skip blank lines and comments
case "$unit" in
''|\#*) continue ;;
esac
# Each unit must appear as a backticked token somewhere in UNITS.md
if ! grep -qF "\`${unit}\`" "$doc"; then
echo "::error::E6: docs/systemd/UNITS.md missing row for unit: ${unit}"
missing=$((missing + 1))
fail=1
fi
done < "$list"
[ "$fail" = "0" ] || { echo "::error::E6: ${missing} unit(s) missing from $doc"; exit 1; }
echo "✓ MFST-C6 / E6: PASS (every unit in $list has a backticked row in $doc)"
# =====================================================================
# NEW CHECKS (v1.50.1 — Distribution Compatibility Audit)
# =====================================================================
- name: Distro config completeness
run: |
echo "=== Validating distribution config files ==="
./cli/lib/nftban/tests/validate_distro_configs.sh etc/nftban/distros/
- name: Hardcoded path detector
run: |
echo "=== Checking for hardcoded binary paths ==="
#
# Shell scripts should use distro config variables, not hardcoded paths.
# Allowed exceptions:
# - etc/nftban/distros/*.conf (distro configs define the paths)
# - cli/lib/nftban/tests/ (test fixtures)
# - cli/lib/nftban/data/ (FHS spec, schemas)
# - install/ (install scripts target known paths)
# - packaging/ (RPM/DEB specs target known paths)
# - .github/ (CI scripts)
#
VIOLATIONS=0
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
# Detect hardcoded /var/log/secure or /var/log/auth.log in runtime code
# These differ between RHEL (/var/log/secure) and Debian (/var/log/auth.log)
grep -rn '/var/log/secure\|/var/log/auth\.log' \
--include="*.sh" \
cli/lib/nftban/core/ \
cli/lib/nftban/cli/ \
cli/lib/nftban/lib/ \
cli/lib/nftban/setup/ \
2>/dev/null | \
grep -v '#.*hardcoded\|DISTRO_PATHS\|fallback\|default\|example\|# ' | \
grep -v 'nftban_distro_config\.sh' \
>> "$TMPFILE" || true
COUNT=$(wc -l < "$TMPFILE" | tr -d ' ')
if [[ "$COUNT" -gt 0 ]]; then
echo "⚠ Found $COUNT hardcoded auth log paths (should use DISTRO_PATHS[auth_log]):"
cat "$TMPFILE"
echo ""
echo "NOTE: Reporting as warning — fix in future PR"
else
echo "✓ No hardcoded auth log paths in runtime code"
fi
# Detect hardcoded /sbin/nft (Debian legacy path) — should always be /usr/sbin/nft or variable
grep -rn '"/sbin/nft"\|/sbin/nft ' \
--include="*.sh" --include="*.go" \
cli/lib/nftban/core/ \
cli/lib/nftban/cli/ \
cli/lib/nftban/lib/ \
cmd/ internal/ \
2>/dev/null | \
grep -v '#.*\|//.*\|test\|example' \
> "$TMPFILE" || true
COUNT=$(wc -l < "$TMPFILE" | tr -d ' ')
if [[ "$COUNT" -gt 0 ]]; then
echo ""
echo "::error::Found $COUNT references to /sbin/nft (legacy path) — use /usr/sbin/nft or distro config"
cat "$TMPFILE"
VIOLATIONS=1
else
echo "✓ No legacy /sbin/nft paths"
fi
if [[ $VIOLATIONS -gt 0 ]]; then
exit 1
fi
echo ""
echo "✓ Hardcoded path check: PASS"
- name: systemd directive version guard
run: |
echo "=== Checking systemd directives against minimum version (249) ==="
#
# NFTBan supports systemd 249+ (EL9 baseline).
# Flag directives that require newer systemd versions.
#
VIOLATIONS=0
# Directives requiring systemd >= 250+
FORBIDDEN_DIRECTIVES=(
"SetCredential=" # 250
"LoadCredential=" # 250
"LoadCredentialEncrypted=" # 250
"RuntimeRandomizedExtraSec=" # 251
"ExecSearchPath=" # 252
"OpenFile=" # 253
"MemoryZSwapMax=" # 254
"CoredumpReceive=" # 255
"ExitType=" # 256
"SurviveFinalKillSignal=" # 256
)
for directive in "${FORBIDDEN_DIRECTIVES[@]}"; do
name="${directive%=}"
if grep -rn "$directive" install/systemd/ 2>/dev/null | grep -v '^#' | grep -v '#.*'; then
echo "::error::Found $name in systemd units — requires systemd > 249 (min supported)"
VIOLATIONS=$((VIOLATIONS + 1))
fi
done
if [[ $VIOLATIONS -gt 0 ]]; then
exit 1
fi
echo "✓ All systemd directives compatible with systemd 249+ (EL9 baseline)"
- name: nft template placeholder guard
run: |
echo "=== Verifying boot-safety of shipped nft config ==="
#
# Invariant: install/nftables/nftables.conf must NEVER contain placeholders.
# It is included by systemd nftables.service at boot — raw placeholders = no firewall.
#
# The template (nftables.conf.tpl) SHOULD have placeholders.
#
# 1. Rendered config must be placeholder-free
if grep -qE '__[A-Z_]{3,}__' install/nftables/nftables.conf 2>/dev/null; then
echo "::error::install/nftables/nftables.conf contains placeholders — BOOT UNSAFE!"
echo "This file is loaded by nftables.service at boot. Placeholders cause nft syntax errors."
echo "Render with safe defaults: __SSH_PORT__→22, __CT_LIMIT_SSH__→15, etc."
grep -n '__[A-Z_]\+__' install/nftables/nftables.conf
exit 1
fi
echo "✓ Rendered config (nftables.conf) is placeholder-free"
# 2. Template must HAVE placeholders (sanity check)
if [[ -f install/nftables/nftables.conf.tpl ]]; then
if ! grep -qE '__[A-Z_]{3,}__' install/nftables/nftables.conf.tpl; then
echo "::error::install/nftables/nftables.conf.tpl has NO placeholders — template is broken"
exit 1
fi
echo "✓ Template (nftables.conf.tpl) contains placeholders"
fi
# 3. Safe config must not have CT_LIMIT placeholders
if [[ -f install/nftables/nftables-safe.conf ]]; then
if grep -qE '__CT_LIMIT_[A-Z]+__' install/nftables/nftables-safe.conf; then
echo "::error::nftables-safe.conf contains CT_LIMIT placeholders — must use hardcoded defaults"
exit 1
fi
echo "✓ Safe config has no CT_LIMIT placeholders"
fi
echo ""
echo "✓ Boot-safety check: PASS"
# v1.64.0: Anchor jump placement lint (static analysis — no nft binary needed)
- name: Lint jump placement (G1-G3)
run: bash scripts/lint-jump-placement.sh
# v1.66.3: Bash hazard lint — nft -a flag + set -e traps
- name: Lint bash hazards (H1-H2)
run: bash scripts/lint-bash-hazards.sh
# v1.70.0: Update safety lint — rebuild failure must be fatal, no fallback reload
- name: Lint update safety (U1-U2)
run: bash scripts/lint-update-safety.sh
# v1.72.0: RPM postinst source-leak regression test
- name: Test RPM postinst source leak (T1-T2)
run: bash scripts/test-rpm-postinst-source-leak.sh
# v1.64.0: Anchor marker count in template
- name: Validate anchor markers in template
run: |
echo "=== Verifying NFTBAN_ANCHOR markers ==="
TPL="install/nftables/nftables.conf.tpl"
CONF="install/nftables/nftables.conf"
for f in "$TPL" "$CONF"; do
count=$(grep -c 'NFTBAN_ANCHOR:ANCHOR_' "$f" || true)
if [[ "$count" -ne 28 ]]; then
echo "::error::${f}: expected 28 NFTBAN_ANCHOR markers, found ${count}"
exit 1
fi
echo "✓ ${f}: ${count} anchor markers"
done
echo ""
echo "✓ Anchor marker check: PASS"
# v1.77.0: Registry parity lint — completion matches registry
# (G15-A; G15-B man-page parity removed in V123 B-1 / PR #646 +
# residual-refs PR — man-page source decommissioned in favour of
# the registry-canonical `nftban help` + Wiki + completion).
- name: Lint registry parity (G15)
run: bash scripts/lint-registry-parity.sh
# =====================================================================
# v1.79.0: Truth and Rebuild Safety Gates (G16-G18)
# =====================================================================
# G16: Validator unit tests - already covered by secure-go.yml (go test ./...)
# The validator tests in internal/validator/validator_test.go run as part of
# the standard Go test suite. This comment documents the gate for traceability.
# G17: Health JSON schema validation
- name: Lint health JSON schema (G17)
run: bash scripts/lint-health-json-schema.sh
# G18: Rebuild safety simulation
- name: Test rebuild safety patterns (G18)
run: bash scripts/test-rebuild-safety.sh
# =====================================================================
# v1.84 M84-4: Contract Freeze Gates
# =====================================================================
# G1-1: Vocabulary enforcement — no banned terms in CLI output
- name: Banned phrase scan (G1-1)
run: bash cli/lib/nftban/tests/test_banned_phrases.sh
# G2-3: Schema version guard — Go source matches CLI expectations
- name: Schema version guard (G2-3)
run: bash cli/lib/nftban/tests/test_schema_version_guard.sh
# =====================================================================
# v1.85 M85-3: Module Completeness Gates
# =====================================================================
# G8-1/G8-2/G8-3 run as Go tests (already in ci-go.yml via go test ./...)
# G8-4 is a runtime smoke test — skips gracefully in CI (no validator binary)
- name: Module smoke test (G8-4)
run: bash cli/lib/nftban/tests/test_module_selftest.sh
# =====================================================================
# v1.134 PR-D: help/code subcommand correlation gates
# =====================================================================
# Doctest guard: every primary-dispatch subcommand a command accepts must
# be documented in help, declared in the intentional-alias allowlist, or
# structural. Catches NEW undocumented subcommands (the drift class that
# produced D-05..D-11). Unparseable families are SKIPPED, never failed.
- name: Help/code doctest guard (v1.134 PR-D)
run: bash cli/lib/nftban/tests/v130_subcommand_flag_help_code_test.sh
# Allowlist integrity + D-26 (health rbl/fhs documented) + D-28 (metrics
# error-usage complete); fails if an allowlisted token is not actually
# dispatched (no phantom allowlist entries).
- name: Alias allowlist integrity (v1.134 PR-D P3)
run: bash cli/lib/nftban/tests/v134_pr_d_p3_alias_allowlist_test.sh
# =====================================================================
# v1.139.2 hotfix: CLI safety + truth-telling gates
# =====================================================================
# Rollback help-guard: `nftban rollback --help` MUST show help text and
# MUST NOT trigger a real rollback. Prior to v1.139.2 the dispatcher
# invoked _do_rollback without parsing --help; a CLI-audit invocation
# downgraded a host. Defense-in-depth guard is at _do_rollback's entry
# so every current and future caller is safe.
- name: Rollback --help guard (v1.139.2)
run: bash cli/lib/nftban/tests/rollback_help_guard_v1_139_2_test.sh
# CLI exit-code contract: bogus subcommand, ban (no arg), ban (invalid
# IP), and update --dry-run MUST return rc>=1 (declared by `nftban --help`
# itself: "0 Success / 1 Error / 2 Warning"). Locks the current correct
# behavior so a future PR cannot regress to silently swallowing errors
# in `nftban X || alert`-style automation.
- name: CLI exit-code contract (v1.139.2)
run: bash cli/lib/nftban/tests/cli_error_rc_contract_v1_139_2_test.sh
# =====================================================================
# v1.144.0 PR-D: D-UXV-13 runtime-hint reachability guard (class-killer)
# =====================================================================
# Reverse-direction guard for the doctest pair at lines 510 + 516 above:
# the v130 forward guard checks that every dispatched subcommand is
# documented; this v144 reverse guard checks that every echo/printf
# 'nftban X Y' literal in cli/sbin/nftban + cli/lib/nftban/cli/cmd_*.sh
# resolves to a reachable command. Catches the drift class that
# produced D-UXV-14 (cmd_connector.sh:234 'nftban connector edit' —
# no edit arm) and D-UXV-15 (cmd_port.sh four sites 'nftban reload' /
# 'nftban port reload' — neither command exists; canonical is
# 'nftban firewall reload'). Would also have caught the 2026-06-01
# audit's incorrect-by-half proposed replacement.
#
# T3-3/4/5 inside the test are the HARD pass criteria (would-have-
# caught the 3 historical broken hints). T3-2 reports legacy
# parser-heuristic drift as a v1.145.x sweep candidate (does not
# block CI). New broken hints introduced in any future PR will
# surface as additional violations and require explicit fix or
# allowlist entry with `reason` field.
- name: Runtime-hint reachability guard (v1.144.0 PR-D D-UXV-13)
run: bash cli/lib/nftban/tests/cli_runtime_hint_reachability_v144_test.sh
# Companion that locks the allowlist's `reason` field requirement +
# restates the would-have-caught hypotheticals as defense-in-depth.
- name: Help-block reachability companion (v1.144.0 PR-D)
run: bash cli/lib/nftban/tests/cli_help_block_reachability_v144_test.sh
# =====================================================================
# v1.145 SSH-port lifecycle guards (PR-A/PR-B/PR-C2 + PR-G wiring)
# =====================================================================
# These static guards lock the set-driven SSH brute-force rate-limit
# (`tcp dport @ssh_ports ct count`): the ssh_ports set in every template,
# its presence in the schema inventory (both families), both-set
# (tcp_ports_in + ssh_ports) runtime parity, union detection, and the
# apply-path hardening. They existed since v1.145 but were never wired
# into CI (v1.145 PR-G audit finding); without these `run:` steps the
# guards provided zero regression protection.
- name: SSH ssh_ports template set-driven guard (v1.145 PR-A)
run: bash cli/lib/nftban/tests/v145_ssh_port_template_set_driven_static_test.sh
- name: SSH union detection guard (v1.145 PR-B)
run: bash cli/lib/nftban/tests/v145_ssh_port_detect_test.sh
- name: SSH both-set parity runtime guard (v1.145 PR-B)
run: bash cli/lib/nftban/tests/v145_pr_b_runtime_static_guard_test.sh
- name: SSH apply-path hardening guard (v1.145 PR-C2)
run: bash cli/lib/nftban/tests/v145_pr_c2_apply_hardening_test.sh
- name: ssh_ports systemic-alignment invariants (v1.145 PR-G)
run: bash cli/lib/nftban/tests/v145_pr_g_ssh_ports_invariants_test.sh
# External admin SSH-port guard (OBS-SSHPORT-55000-FAMILY): warn-only +
# lockout-net. Asserts the guard warns on an external :55000->:22 redirect,
# session-whitelists the admin IP via the IPC whitelist-session path only
# (no direct nft write), keeps ssh_ports = {22} (REJECT import), and is a
# no-op off-session / on dry-run / on opt-out / on re-entry.
- name: External admin SSH-port guard (OBS-SSHPORT-55000)
run: bash cli/lib/nftban/tests/ssh_admin_port_guard_v150_test.sh
# v1.155 PR-1 (item 3.2): socket-activated sshd port-mismatch warning.
# Asserts the guard warns ONLY when configured (sshd -T) != listening (ss)
# AND sshd is socket-activated, prints the exact remediation hint
# (`systemctl daemon-reload && systemctl restart ssh.socket`), and is
# silent when ports match or on a plain sshd.service. Hermetic.
- name: Socket-activated SSH port-mismatch warning (v1.155 PR-1)
run: bash cli/lib/nftban/tests/ssh_socket_port_mismatch_v155_test.sh
# v1.155 PR-2 (item 3.3): SSH-port-change lifecycle validator invariants.
# Exercises tools/validation/ssh_port_change_lifecycle_validate.sh against a
# good rendered-ruleset fixture (exit 0) and four injected drifts (listener
# not in ssh_ports / not in tcp_ports_in; literal `tcp dport 22 ct count`
# instead of @ssh_ports; missing @ssh_ports rule), each failing with the
# right invariant message. Hermetic (inputs injected via env).
- name: SSH-port-change lifecycle validator (v1.155 PR-2)
run: bash cli/lib/nftban/tests/ssh_port_change_lifecycle_v155_test.sh
# v1.161: default-enabled-timer first-run guard (geoban lesson, v1.156->v1.159).
# Enumerates the installer's auto-enabled timers (coreTimers in
# internal/installer/services/timers.go) and asserts each timer's .service
# EITHER has all ExecStart deps shipped/always-present OR carries an
# ExecCondition that SKIPS (not fails) when an optional dep is absent — so a
# default-enabled timer cannot hard-fail on a package-default host and flip
# INSTALL_STATE=DEGRADED (the v1.156 nftban-geoban-refresh regression).
# Hermetic / repo-static (no systemd, no host).
- name: Default-enabled timer first-run guard (v1.161)
run: bash cli/lib/nftban/tests/default_enabled_timer_first_run_guard_v161.sh
# v1.162 PR-A (DELTA §3.1): SSH durable multi-port render + reboot-sim.
# Sources cmd_firewall.sh's _firewall_substitute_placeholders, mocks the
# SSH detector to a multi-port union (22, 2222, 55000), renders the real
# install/nftables/nftables.conf.tpl, and asserts the durable `set
# ssh_ports` carries the FULL union (not primary-only) in BOTH families,
# the SSH ct-count rule stays set-driven (`tcp dport @ssh_ports`), no
# __SSH_PORT__ placeholder survives, and (reboot-sim) the durable file
# alone re-reads with every union port intact (no kernel reconcile).
# Hermetic (TMPDIR sandbox; no host, no systemd, no sshd).
- name: SSH durable multi-port render + reboot-sim (v1.162 PR-A)
run: bash cli/lib/nftban/tests/ssh_durable_multiport_render_v162.sh
# v1.162 PR-B (DELTA §3.5): static fallback install/nftables/nftables.conf
# must be set-driven — `set ssh_ports` defined in both ip/ip6 tables and the
# SSH ct-count rule reads `tcp dport @ssh_ports` (no literal `tcp dport 22`),
# set defined-before-use. Static grep guard (the boot-baseline `nft -c`
# parse is exercised by the package install/runtime CI legs).
- name: Static nftables fallback is set-driven (v1.162 PR-B)
run: bash cli/lib/nftban/tests/static_nftables_ssh_set_driven_v162.sh
# v1.165 PR-A: generated-spec comment-macro lint. rpm 4.16 (EL9/EL10)
# parses a bare unescaped %install token in a SPEC COMMENT as a second
# %install section (`error: second %install`) and fails Build RPM
# el9/el10 deterministically — this bit twice (v1.157 FIX_V157_PR_A,
# v1.164 PR-C revert) because rpm 4.18 (lab2) + rpm 6.x are lenient and
# miss it. This hermetic static guard catches the class BEFORE the
# expensive el9/el10 rpmbuild: zero %install outside the single section
# header, and no bare RPM section macro in any generated-spec comment.
- name: RPM spec has no section macro in a comment (v1.165 PR-A)
run: bash cli/lib/nftban/tests/rpm_spec_no_section_macro_in_comment_v165_test.sh
# v1.165 PR-B: the %files section both %include nftban-files.inc (which
# %dir-owns every /usr/lib/nftban payload dir) and used to repeat those
# dirs as bare payload paths, so strict rpm 4.16 warned "File listed
# twice". PR-B lists the 12 lib dirs as <dir>/* (inc keeps sole %dir
# ownership) and strips dotfiles in %install so the /* glob (which skips
# dotfiles) does not orphan tests/.gitkeep. This static guard asserts the
# dedup shape + the dotfile strip; the full no-double-listing / no-orphan
# parse is confirmed by Build RPM el9/el10.
- name: RPM %files lib-dir dedup (no double dir listing) (v1.165 PR-B)
run: bash cli/lib/nftban/tests/rpm_files_no_double_dir_listing_v165_test.sh
# v1.166 PR-C: templates %files dedup via FHS-generator correction. The
# bare /usr/share/nftban/templates line both double-listed the generator
# %dir-owned dirs and solely owned the email/+partials/ staged .html (no
# inc %dir), so it could not be dropped naively. PR-C adds %dir for
# templates/email + templates/partials to build/fhs-spec.yaml (regenerated
# into nftban-files.inc), lists the 4 content subdirs as <dir>/*, keeps
# zabbix as an empty %dir, and strips dotfiles in %install. This guard
# asserts the dedup shape + generator ownership; the no-orphan/no-double
# parse is confirmed by Build RPM el9/el10. (FHS body changes by design —
# the "FHS spec generated files check" gate enforces generator parity.)
- name: RPM %files templates dedup (FHS-generator correction) (v1.166 PR-C)
run: bash cli/lib/nftban/tests/rpm_files_templates_dedup_v166_test.sh
# v1.167 UX-residual lane guards (were merged un-wired; wired here as part
# of the v1.167 blocker fix so future regressions are caught in CI).
- name: Feed-counter unification (v1.167 PR-1, BUG-CtCount-feeds)
run: bash cli/lib/nftban/tests/feed_counters_unify_v167_test.sh
- name: CLI output-truth + flag-parity (v1.167 PR-2)
run: bash cli/lib/nftban/tests/cli_output_truth_v167_test.sh
- name: No re-introduced orphan modules (v1.167 PR-3)
run: bash cli/lib/nftban/tests/no_reintroduced_orphan_modules_v167_test.sh
# v1.167 SHIP-BLOCKER guard: `nftban update --dry-run` must return rc=1 with
# the 3-line hint (NOT command-not-found rc=127). Also asserts the dispatcher
# sources cmd_common.sh so the 6 CLI-BUG-1 sibling error paths are reachable.
- name: update --dry-run hint rc=1 + CLI-BUG-1 reachability (v1.167)
run: bash cli/lib/nftban/tests/cli_update_dry_run_hint_v167_test.sh
# v1.168 CLI-BUG-2 whitelist-TTL lane guards: the 4 whitelist set decls
# must carry interval,timeout (render prereq) AND the DEB/RPM upgrade path
# must re-activate timeout-capable sets (GetOrCreateIntervalSet reuses
# existing flagless sets). The TTL-survives-FullSync invariant is a Go test
# (internal/setsync) run by ci-go.yml.
- name: Whitelist sets carry timeout flag (v1.168 render prereq)
run: bash cli/lib/nftban/tests/whitelist_set_timeout_flag_v168_test.sh
- name: Whitelist-TTL upgrade activation backstop (v1.168 deb+rpm)
run: bash cli/lib/nftban/tests/whitelist_timeout_upgrade_activation_v168_test.sh
# v1.169 CLI-BUG-3: `whitelist list` labels timed/session vs durable entries.
- name: Whitelist list timed/session label (v1.169 CLI-BUG-3)
run: bash cli/lib/nftban/tests/whitelist_list_timed_label_v169_test.sh
# v1.169 CI-TEST-GAP: wire 6 substantive guards that shipped with their
# lanes (v1.158–v1.163) but were never added to any workflow. Each is
# hermetic (no host/root) — they only catch a future regression once run.
- name: MAC posture advisory surface (v1.158)
run: bash cli/lib/nftban/tests/mac_posture_v158_test.sh
- name: Geoban-refresh ExecCondition skip-not-fail (v1.159)
run: bash cli/lib/nftban/tests/geoban_refresh_execcondition_v159_test.sh
- name: Firewall-pkg state-aware wording (v1.160)
run: bash cli/lib/nftban/tests/firewall_pkg_wording_v160.sh
- name: Permissions optional-module skip (v1.160)
run: bash cli/lib/nftban/tests/permissions_optional_module_skip_v160.sh
- name: Whitelist verify read-only (v1.163)
run: bash cli/lib/nftban/tests/whitelist_verify_v163_test.sh
- name: Immutable-flag health verify (v1.163)
run: bash cli/lib/nftban/tests/immutable_health_verify_v163_test.sh
# v1.170 BUG-STATS-IP-HISTORY: `nftban stats ip <IP>` must single-emit (no
# pipefail double-`[]` arith crash for zero-record IPs) AND read compressed/
# rotated bans.log archives (zgrep); + gzip dep stays declared.
- name: stats ip history pipefail + compressed-log fix (v1.170)
run: bash cli/lib/nftban/tests/stats_ip_history_v170_test.sh
# v1.171 §4.2/§4.3/§3.6: daemon state writes are atomic (route through
# safety.SafeWriteFile); ssh_ports counter seeded. The atomic mechanism
# itself is proven by Go test internal/safety/atomic_write_v171_test.go.
- name: state-write atomicity call-site lock (v1.171)
run: bash cli/lib/nftban/tests/state_write_atomicity_v171_test.sh
# v1.172 CLI-PIPEFAIL-ARITH sweep: zero-match grep / SIGPIPE / jq-parse failures
# under set -Eeuo pipefail must not double-emit a numeric ("0\n0") that crashes a
# downstream [[ -eq/-gt ]] / jq tonumber. Behavioral single-emit checks + static
# lint that the previously-broken arith-fed sites no longer carry the unsafe forms.
- name: CLI pipefail-arith sweep (v1.172)
run: bash cli/lib/nftban/tests/cli_pipefail_arith_sweep_v172_test.sh
# v1.173 §4.1 session-whitelist flock (shell side): each of the 3
# cmd_firewall.sh session RMW funcs serializes its read-modify-write under
# flock(9) on the shared cross-language lock /run/nftban/session_whitelist.lock
# (same path the Go installer flock(2)s). Go side is covered by
# internal/installer/safety/session_whitelist_flock_v173_test.go.
- name: session-whitelist flock shell-side (v1.173)
run: bash cli/lib/nftban/tests/session_whitelist_flock_v173_test.sh
# v1.174 HEALTH-OOM-JOURNALCTL: every actual journalctl invocation on the
# nftban-health.service health-check path is bounded (-n/--since), and the
# unit's MemoryMax is raised 128M->256M for the validator + concurrent-
# children RSS on large journals. The Go validator read is already bounded
# (internal/validator/journal.go --since -15m -n 200). The stale failed-
# unit classification half is covered by Go test
# internal/installer/validate/systemd_payload_v174_test.go.
- name: health journalctl bounded + MemoryMax 256M (v1.174)
run: bash cli/lib/nftban/tests/health_journalctl_bounded_v174_test.sh
# v1.174 ALERT-THROTTLE-NONFATAL: a state-write (throttle/diagnostics/
# failure-metric) permission failure must NOT fail/latch nftban-alert@*.service
# (monitor root cause), but a real alert DELIVERY failure still follows the rc
# contract. cli/sbin/nftban-service-alert.
- name: alert bookkeeping non-fatal, delivery keeps rc (v1.174)
run: bash cli/lib/nftban/tests/alert_bookkeeping_nonfatal_v174_test.sh
# v1.174 update-summary messaging: surface an auto-recovered pre-existing
# failed unit to the operator (transparency) + fix the misleading DEGRADED
# "known transient on the exporter" wording. cli/lib/nftban/cli/cmd_update.sh.
- name: update messaging recovered + DEGRADED (v1.174)
run: bash cli/lib/nftban/tests/update_msg_recovered_v174_test.sh
# v1.175 FHS lane: (T1) GENERIC guard — no root-owned tmpfiles entry under a
# non-root declared parent (the systemd-tmpfiles exit-73 "unsafe path transition"
# class — structural BUG-TMPFILES regression lock); auditors→package +
# firewall-validate→ExecStartPre out of tmpfiles; ALERT-THROTTLE-FHS alerts dir;
# FHS-SMELL-SIDSTATS cache.go DataDir relocation + migration.
- name: FHS lane invariants — tmpfiles transition guard + relocations (v1.175)
run: bash cli/lib/nftban/tests/fhs_lane_v175_test.sh
# v1.176 DAEMON RELIABILITY: FSYNC-RESIDUAL durability-idiom class lock —
# no NEW hand-rolled temp+rename Go state writer outside internal/safety
# (route through safety.SafeWriteFile); regression-guards the F-1/F-2 fixes.
- name: FSYNC-RESIDUAL durability-idiom guard (v1.176)
run: bash cli/lib/nftban/tests/fsync_residual_guard_v176_test.sh
# v1.177 HTTP LOG DISCOVERY: panel-aware access-log discovery helper —
# DirectAdmin/cPanel/Plesk/generic/LiteSpeed globs, multi-log, dedup,
# error/rotated exclusion, MAX_FILES bound, IPv6 parse, incremental offset
# + rotation/copytruncate, and the legacy-miss vs new-find regression.
- name: HTTP log discovery + BotScan panel paths (v1.177)
run: bash cli/lib/nftban/tests/http_log_discovery_v177_test.sh
- name: BotScan read-authority collector + spool-first (v1.178-A)
run: bash cli/lib/nftban/tests/botscan_read_authority_v178_test.sh
- name: BotScan processor deadline + rotation (v1.185 Lane B)
run: bash cli/lib/nftban/tests/botscan_deadline_rotation_v185_test.sh
# =====================================================================
# v1.86 B86-4: Contract Enforcement — Legacy Regression Blockers
# =====================================================================
# These greps prevent reintroduction of deleted legacy constructs.
# If any match, the contract has been violated and CI must fail.
- name: No ModuleTruth reintroduction (B86-1 guard)
run: |
echo "Checking for legacy ModuleTruth references..."
if grep -rn "ModuleTruth\|deriveModuleTruth\|ModuleStatus\|ModuleInfo\|module_truth" \
internal/validator/*.go \
--include="*.go" \
| grep -v "B86-1.*deleted\|B86-1.*removed\|_test.go.*deleted"; then
echo "::error::Legacy ModuleTruth reference found — B86-1 contract violated"
exit 1
fi
echo "OK: no legacy ModuleTruth references"
- name: No legacy fallback reintroduction (M84-2 guard)
run: |
echo "Checking for legacy fallback references..."
if grep -rn "protection_state_legacy\|FORCE_LEGACY_STATE\|nftban_invariant_validator" \
cli/lib/nftban/cli/*.sh \
internal/validator/*.go \
| grep -v "deleted\|removed\|B86\|M84\|v1.84\|v1.86"; then
echo "::error::Legacy fallback reference found — M84-2 contract violated"
exit 1
fi
echo "OK: no legacy fallback references"
# =====================================================================
# SURICATA INVARIANT GATES (v1.92 — GS-001..GS-014)
# Source: V192_FINAL_ARCHITECTURE_DECISION.md
# =====================================================================
- name: "GS-003: Suricata ban_handler uses IPC only"
run: |
echo "Checking ban_handler.go for direct nft calls..."
if grep -n 'exec\.Command.*"nft"' internal/suricata/ban_handler.go 2>/dev/null; then
echo "::error::ban_handler.go contains direct nft command — INV-S-004 violation"
exit 1
fi
echo "OK: ban_handler uses IPC only"
- name: "GS-004: No scope creep reintroduction"
run: |
echo "Checking deleted packages don't exist..."
FAIL=0
for dir in internal/suricata/customrules internal/suricata/recommendations internal/suricata/scanner; do
if [ -d "$dir" ]; then
echo "::error::$dir exists — scope creep reintroduction"
FAIL=1
fi
done
[ $FAIL -eq 0 ] || exit 1
echo "OK: no scope creep directories"
- name: "GS-008: Inline exception disabled by default"
run: |
echo "Checking inline default config..."
if grep -q 'INLINE_EXCEPTION_ENABLED="true"' etc/nftban/modules/inline_exceptions.conf 2>/dev/null; then
echo "::error::Inline exception enabled in default config — INV-S-009 violation"
exit 1
fi
echo "OK: inline exception disabled by default"
- name: "GS-014: Rule engine cannot trigger inline"
run: |
echo "Checking rule engine for inline references..."
if grep -rn 'nfqueue\|inline.*drop\|InlineException' internal/suricata/policy.go 2>/dev/null; then
echo "::error::Rule engine/policy references inline enforcement — INV-S-012 violation"
exit 1
fi
echo "OK: rule engine isolated from inline"
# ====================================================================
# V108 Item 2 — +i lifecycle matrix CI gate
# ====================================================================
# Verifies build/+i-lifecycle-matrix.yaml is in sync with the 4 source
# surfaces that handle chattr +i lifecycle:
# 1. Go SetImmutableFlags (internal/installer/validate/authority.go)
# 2. RPM scriptlets (packaging/build_nftban.sh: %pretrans + %preun)
# 3. DEB scriptlets (packaging/deb/preinst, postinst, prerm)
# The matrix yaml is the single source of truth. Drift between yaml and
# any source surface FAILs the gate.
#
# Closes v1.107.2-class regression where Go SetImmutableFlags extended
# its protected list silently and the scriptlets fell out of sync,
# producing "cpio: rename failed - No data available" on package upgrade.
# ====================================================================
- name: "V108 Item 2: +i lifecycle matrix"
run: |
set -Eeuo pipefail
echo "=== V108 Item 2 — +i lifecycle matrix gate ==="
bash scripts/ci/test-immutable-lifecycle-matrix.sh scan
# ====================================================================
# V108 Item 6 — install-method detection (source-install mismatch)
# ====================================================================
# Validates _detect_install_type + _classify_for_pkg_mgr_update across
# 9 fixtures covering rpm/deb/source/source-git/unknown/mixed/cross-
# family/confirms-db. Closes the dns2-class defect where source-
# installed hosts fell through to "unknown" because the detector
# didn't read update-history.json first-entry "type".
# ====================================================================
- name: "V108 Item 6: install-method detection"
run: |
set -Eeuo pipefail
echo "=== V108 Item 6 — install-method detection gate ==="
bash scripts/ci/test-install-method-detection.sh suite
# ====================================================================
# V108 Item 3 — heredoc command-substitution safety gate
# ====================================================================
# Detects unsafe shell expansion (unescaped backticks, $(...), $((...)))
# in unquoted heredoc bodies in build/generator scripts.
#
# Defends against the v1.107.1-class defect (PR #585 fixup da14e182):
# markdown backticks in unquoted RPM-spec heredoc were interpreted by
# bash as command substitution at heredoc-write time, corrupting the
# generated spec content. el10's rpmbuild failed; el9's tolerated.
#
# Scope: packaging/build_nftban.sh, build/generate-*.sh, scripts/*.sh,
# scripts/ci/*.sh. Function-context heredocs (usage()) are auto-
# detected and allowed to use $(...) at function-call time.
# ====================================================================
- name: "V108 Item 3: heredoc command-substitution safety"
run: |
set -Eeuo pipefail
echo "=== V108 Item 3 — heredoc-safety gate ==="
bash scripts/ci/test-heredoc-safety.sh scan
# ====================================================================
# V110 R-10 — Module Isolation Lint
# ====================================================================
# Enforces three invariants over registered Module implementers:
# A1 — Distinct ModuleName across all `func (m *Module) Name() string`
# implementers (rejects accidental name reuse)
# A2 — Every `eventbus.NewEvent(eventbus.EventBan, ...)` publish uses
# the calling module's own ModuleName const (rejects cross-
# attribution and string-literal sources)
# A3 — Status().Extra cross-module key isolation: locks the current
# baseline of legitimate shared keys (`mode`, `suricata_available`,
# `tracked_ips`) and blocks NEW cross-module key introductions
#
# Schema impact: NONE. Lint is repository-static.
# Origin: V110 R-10 (FM-J) per AUDIT_190_MODULE_ISOLATION/REMEDIATION_PLAN.md
# Workspace anchor: V110_MOD_ISOLATION_DELTA_AUDIT.md §5
# ====================================================================
- name: "V110 R-10: module isolation lint"
run: |
set -Eeuo pipefail
echo "=== V110 R-10 — module isolation lint ==="
bash scripts/lint-module-isolation.sh