Skip to content

Update README.md

Update README.md #47

Workflow file for this run

# ==============================================================================
# GitHub Actions CI Pipeline for Apotropaios - Firewall Manager
# Description: Multi-stage CI with syntax check, lint, security scan, unit tests,
# integration tests, security tests, and multi-distro matrix.
# Version: 1.1.5
# ==============================================================================
name: CI
on:
push:
branches: [main, develop, 'feature/**', 'fix/**']
pull_request:
branches: [main, develop]
workflow_dispatch:
inputs:
debug_enabled:
description: 'Enable debug logging'
required: false
default: 'false'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
security-events: write
jobs:
# ===========================================================================
# Stage 1: Syntax Check (fastest gate)
# ===========================================================================
syntax:
name: Syntax Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Bash syntax check
run: make syntax-check
# ===========================================================================
# Stage 2: ShellCheck Lint
# ===========================================================================
lint:
name: ShellCheck Lint
needs: syntax
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install ShellCheck
run: sudo apt-get install -y shellcheck
- name: Run ShellCheck
run: make lint
# ===========================================================================
# Stage 3: Security Scan (static analysis + security tests)
# ===========================================================================
security:
name: Security Scan
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install BATS
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
sudo /tmp/bats/install.sh /usr/local
- name: Install ShellCheck
run: sudo apt-get install -y shellcheck
- name: Security-focused ShellCheck (severity=warning)
run: |
echo "==> Security-focused static analysis..."
find . -name '*.sh' -not -path './dist/*' -not -path './.git/*' -not -path './data/*' | while read -r f; do
shellcheck -S warning -x "$f" 2>/dev/null || true
done
- name: Check for dangerous patterns
run: |
echo "==> Scanning for dangerous patterns..."
found=0
# Check for eval with user-supplied data (exclude framework-internal eval)
# Known-safe: security_scrub_vars (var scrubbing), _CLEANUP_STACK (cleanup),
# error_with_fallback (primary/fallback), util_parallel_exec (framework cmd)
matches=$(grep -rn 'eval.*\$' lib/ apotropaios.sh --include='*.sh' \
| grep -v 'eval "exec' \
| grep -v '^\s*#' \
| grep -v 'security_scrub_vars\|_CLEANUP_STACK\|cleanup\|scrub\|error_with_fallback\|util_parallel' \
|| true)
if [ -n "$matches" ]; then
echo "$matches"
echo "::warning::Found eval with variable expansion outside known-safe patterns (review required)"
found=1
fi
# Check for unquoted command substitution in dangerous contexts
# Exclude: variable assignments (local/readonly/export), arithmetic,
# log messages, printf arguments, and string interpolation in quotes
matches=$(grep -rn '\$(' lib/ apotropaios.sh --include='*.sh' \
| grep -v '^\s*#' \
| grep -v 'local \|readonly \|export \|="\$(\|="$(' \
| grep -v 'log_\|printf\|echo' \
| grep -v '\$((\|util_\|basename\|dirname\|date\|wc\|grep\|cat\|head\|cut' \
|| true)
if [ -n "$matches" ]; then
echo "$matches"
echo "::notice::Found potentially unquoted command substitution (review recommended)"
found=1
fi
# Check for /tmp without mktemp
matches=$(grep -rn '/tmp/' lib/ apotropaios.sh --include='*.sh' \
| grep -v '#\|mktemp\|test\|TMPDIR\|printf\|help\|echo' \
|| true)
if [ -n "$matches" ]; then
echo "$matches"
echo "::warning::Found hardcoded /tmp paths — use mktemp instead"
found=1
fi
if [ "$found" -eq 0 ]; then
echo " ✓ No dangerous patterns found"
fi
echo "==> Pattern scan complete"
- name: Run security tests
run: |
mkdir -p test-results
bats tests/security/ --tap > test-results/security.tap 2>&1 || {
echo "::error::Security tests failed"
cat test-results/security.tap
exit 1
}
echo "Security tests passed: $(grep -c '^ok ' test-results/security.tap)"
- name: Upload security results
if: always()
uses: actions/upload-artifact@v6
with:
name: security-scan-results
path: test-results/
retention-days: 30
# ===========================================================================
# Stage 4a: Unit Tests
# ===========================================================================
unit-tests:
name: Unit Tests (${{ matrix.os }})
needs: lint
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- name: Install BATS
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
sudo /tmp/bats/install.sh /usr/local
- name: Run unit tests
run: |
mkdir -p test-results
bats tests/unit/ --tap > test-results/unit.tap 2>&1 || {
cat test-results/unit.tap; exit 1
}
echo "Unit: $(grep -c '^ok ' test-results/unit.tap) passed"
- name: Upload results
if: always()
uses: actions/upload-artifact@v6
with:
name: unit-results-${{ matrix.os }}
path: test-results/
retention-days: 14
# ===========================================================================
# Stage 4b: Integration Tests
# ===========================================================================
integration-tests:
name: Integration Tests (${{ matrix.os }})
needs: lint
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
- os: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- name: Install BATS
run: |
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
sudo /tmp/bats/install.sh /usr/local
- name: Run integration tests
run: |
mkdir -p test-results
bats tests/integration/ --tap > test-results/integration.tap 2>&1 || {
cat test-results/integration.tap; exit 1
}
echo "Integration: $(grep -c '^ok ' test-results/integration.tap) passed"
- name: Upload results
if: always()
uses: actions/upload-artifact@v6
with:
name: integration-results-${{ matrix.os }}
path: test-results/
retention-days: 14
# ===========================================================================
# Stage 5: Multi-Distro Container Tests
# ===========================================================================
distro-tests:
name: Distro (${{ matrix.name }})
needs: lint
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: Debian-12
container: 'debian:12'
pkg_manager: apt
- name: Kali-Linux
container: 'kalilinux/kali-rolling'
pkg_manager: apt
- name: Rocky-9
container: 'rockylinux:9'
pkg_manager: dnf
- name: Alma-9
container: 'almalinux:9'
pkg_manager: dnf
- name: Arch
container: 'archlinux:latest'
pkg_manager: pacman
container:
image: ${{ matrix.container }}
steps:
- name: Install prerequisites (apt)
if: matrix.pkg_manager == 'apt'
run: |
apt-get update -qq
apt-get install -y bash git curl make coreutils procps util-linux
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
cd /tmp/bats && ./install.sh /usr/local
- name: Install prerequisites (dnf)
if: matrix.pkg_manager == 'dnf'
run: |
dnf install -y --allowerasing bash git curl make coreutils procps-ng util-linux
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
cd /tmp/bats && ./install.sh /usr/local
- name: Install prerequisites (pacman)
if: matrix.pkg_manager == 'pacman'
run: |
pacman -Sy --noconfirm bash git curl make coreutils procps-ng util-linux
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats
cd /tmp/bats && ./install.sh /usr/local
- uses: actions/checkout@v6
- name: Run all tests
run: |
mkdir -p test-results
bats tests/unit/ --tap > test-results/unit.tap 2>&1 || { cat test-results/unit.tap; exit 1; }
bats tests/integration/ --tap > test-results/integration.tap 2>&1 || { cat test-results/integration.tap; exit 1; }
bats tests/security/ --tap > test-results/security.tap 2>&1 || { cat test-results/security.tap; exit 1; }
u=$(grep -c '^ok ' test-results/unit.tap)
i=$(grep -c '^ok ' test-results/integration.tap)
s=$(grep -c '^ok ' test-results/security.tap)
echo "${{ matrix.name }}: ${u} unit + ${i} integration + ${s} security = $((u+i+s)) total"
- name: Upload results
if: always()
uses: actions/upload-artifact@v6
with:
name: distro-results-${{ matrix.name }}
path: test-results/
retention-days: 14
# ===========================================================================
# Stage 6: Test Summary
# ===========================================================================
test-summary:
name: Test Summary
needs: [unit-tests, integration-tests, security, distro-tests]
runs-on: ubuntu-latest
if: always()
steps:
- name: Download all results
uses: actions/download-artifact@v6
with:
path: all-results/
- name: Generate summary
run: |
echo "## Test Results Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Stage | Result |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Unit Tests | ${{ needs.unit-tests.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Integration Tests | ${{ needs.integration-tests.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Security Scan | ${{ needs.security.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Distro Tests | ${{ needs.distro-tests.result }} |" >> $GITHUB_STEP_SUMMARY
- name: Check all jobs
run: |
if [ "${{ needs.unit-tests.result }}" != "success" ]; then
echo "::error::Unit tests failed"; exit 1
fi
if [ "${{ needs.integration-tests.result }}" != "success" ]; then
echo "::error::Integration tests failed"; exit 1
fi
if [ "${{ needs.security.result }}" != "success" ]; then
echo "::error::Security scan failed"; exit 1
fi
if [ "${{ needs.distro-tests.result }}" != "success" ]; then
echo "::error::Distro tests failed"; exit 1
fi
echo "All CI stages passed"