<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: automating-security-baseline-checks
description: >-
  Automate security baseline compliance checking against CIS Benchmarks,
  DISA STIGs, and custom hardening standards. Covers OS configuration
  auditing, service enumeration, file permission checks, and drift
  detection with structured pass/fail reporting.
domain: cybersecurity
subdomain: automation-scripting
tags:
  - cis-benchmarks
  - stig
  - hardening
  - compliance-audit
  - baseline-check
  - configuration-drift
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1082"]
---

# Automating Security Baseline Checks

## Overview

Security baselines define the minimum acceptable configuration for systems.
This skill covers building automated checkers that validate OS settings,
file permissions, service states, and network configurations against CIS
Benchmarks, DISA STIGs, or custom baselines. Output is structured for
compliance dashboards and audit evidence.

Mode: `[MODE: BLUE]` — Defensive compliance with drift detection.

## Prerequisites

| Requirement | Details |
|---|---|
| Python 3.10+ with `pyyaml` | Required |
| Root/sudo for reading system configuration files | Required |
| Baseline definitions in YAML format | Required |

## Key Concepts

### Baseline Check Engine

```python
import subprocess
from pathlib import Path

def check_file_permissions(path: str, expected_mode: str, expected_owner: str) -> dict:
    """Verify file permissions and ownership."""
    p = Path(path)
    if not p.exists():
        return {"path": path, "status": "missing", "compliant": False}
    stat = p.stat()
    actual_mode = oct(stat.st_mode)[-4:]
    import pwd
    actual_owner = pwd.getpwuid(stat.st_uid).pw_name
    return {
        "path": path,
        "expected_mode": expected_mode,
        "actual_mode": actual_mode,
        "expected_owner": expected_owner,
        "actual_owner": actual_owner,
        "compliant": actual_mode == expected_mode and actual_owner == expected_owner,
    }

def check_service_disabled(service: str) -> dict:
    """Verify a service is disabled and not running."""
    try:
        result = subprocess.run(
            ["systemctl", "is-enabled", service],
            capture_output=True, text=True, timeout=5,
        )
        enabled = result.stdout.strip()
        running = subprocess.run(
            ["systemctl", "is-active", service],
            capture_output=True, text=True, timeout=5,
        ).stdout.strip()
        return {
            "service": service,
            "enabled": enabled,
            "active": running,
            "compliant": enabled in ("disabled", "masked") and running != "active",
        }
    except FileNotFoundError:
        return {"service": service, "error": "systemctl not found"}

def check_sysctl(key: str, expected: str) -> dict:
    """Verify kernel parameter value."""
    try:
        result = subprocess.run(
            ["sysctl", "-n", key], capture_output=True, text=True, timeout=5,
        )
        actual = result.stdout.strip()
        return {
            "parameter": key,
            "expected": expected,
            "actual": actual,
            "compliant": actual == expected,
        }
    except FileNotFoundError:
        return {"parameter": key, "error": "sysctl not found"}
```

### CIS Benchmark Runner

```python
import yaml

def run_baseline(baseline_file: str) -> dict:
    """Execute all checks defined in a YAML baseline."""
    with open(baseline_file) as f:
        baseline = yaml.safe_load(f)

    results = []
    for check in baseline.get("checks", []):
        if check["type"] == "file_permissions":
            r = check_file_permissions(check["path"], check["mode"], check["owner"])
        elif check["type"] == "service_disabled":
            r = check_service_disabled(check["service"])
        elif check["type"] == "sysctl":
            r = check_sysctl(check["key"], check["value"])
        else:
            r = {"error": f"unknown check type: {check['type']}"}
        r["check_id"] = check.get("id", "")
        r["description"] = check.get("description", "")
        results.append(r)

    passed = sum(1 for r in results if r.get("compliant"))
    return {
        "baseline": baseline.get("name", baseline_file),
        "total_checks": len(results),
        "passed": passed,
        "failed": len(results) - passed,
        "compliance_pct": round(passed / max(len(results), 1) * 100, 1),
        "results": results,
    }
```

## Workflow

### Step 1: Run CIS Baseline Check

```bash
node scripts/agent.js --action check --baseline /opt/baselines/cis-ubuntu-22.yml
```

### Step 2: Single File Permission Check

```bash
node scripts/agent.js --action file-check --path /etc/shadow --mode 0640 --owner root
```

### Step 3: Sysctl Parameter Audit

```bash
node scripts/agent.js --action sysctl-check --key net.ipv4.ip_forward --expected 0
```

## Verification

- [ ] Baseline runner processes all check types in YAML definition
- [ ] File permission checks validate mode and ownership
- [ ] Service checks confirm disabled and inactive state
- [ ] Sysctl checks compare actual vs. expected kernel parameters
- [ ] Exit code 0 on 100% compliant, 1 on failures, 2 on error

## References

- [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks)
- [DISA STIGs](https://public.cyber.mil/stigs/)
- [NIST SP 800-123 — Server Security](https://csrc.nist.gov/publications/detail/sp/800-123/final)
- [CIS Controls v8 — Control 4: Secure Configuration](https://www.cisecurity.org/controls)
