Skip to content

Architecture and Design

Ryan edited this page Jul 11, 2026 · 3 revisions

Architecture and Design

Version: 3.9 Last Updated: June 2026

This page documents the internal architecture of the Linux Security Audit Project, including the v3.0 foundation library, audit pipeline, helper caching layer (v3.6), cross-module correlation (v3.7), per-distribution profiles (v3.7, expanded to 18 in v3.9), the attack-surface assessment and per-framework split reports (v3.8), and the canonical remediation registry and shared physical assessments (v3.9).


High-Level Architecture

The system is organised into three layers:

  1. Modules (modules/) - One file per security framework. Each module performs its own checks, returning a list of AuditResult objects.
  2. Shared Components (shared_components/) - Common infrastructure used by all modules and the orchestrator: result objects, OS detection, caching, parallel execution, correlation registry, risk scoring, baseline comparison, remediation bundles, rollback generation, host_facts.py (v3.7), profiles.py (v3.7), attack_surface.py (v3.8), shared_assessments.py (v3.9), and canonical_remediations.py (v3.9).
  3. Orchestrator (linux_security_audit.py) - Discovers modules, manages execution (sequential or parallel), aggregates results, runs the v3 audit pipeline, applies any selected distribution profile filter, and produces reports.
flowchart TB
    User([Operator])
    CLI["linux_security_audit.py<br/>(orchestrator)"]
    User --> CLI

    subgraph SC ["shared_components/"]
        AC["audit_common.py<br/>(AuditResult, caching,<br/>parallel exec)"]
        OS["os_detection.py<br/>(distro/family,<br/>pretty_name, kernel)"]
        REM["remediation_library.py<br/>(distro-aware<br/>remediation entries)"]
        V3["v3_pipeline.py<br/>(correlation, risk scoring,<br/>baseline diff)"]
        MH["module_helpers.py<br/>(v3.6 caching layer:<br/>command_available,<br/>systemd_active, run_command,<br/>read_sysctl, read_file_safe)"]
        CR["correlation_registry.py"]
        RS["risk_scoring.py"]
        BC["baseline_compare.py"]
        RB["remediation_bundles.py"]
        RG["rollback_generator.py"]
        HF["host_facts.py<br/>(v3.7 cross-module<br/>correlation: HostFacts,<br/>75 derived fields)"]
        PR["profiles.py<br/>(v3.7 per-distribution<br/>profiles: 18 built-in,<br/>subtractive filtering)"]
    end

    subgraph MOD ["modules/ (16 frameworks)"]
        direction LR
        ORIG["Original 8:<br/>Core - CIS - CISA - ENISA<br/>ISO27001 - NIST - NSA - STIG"]
        NEW["Phase 3 (8):<br/>ACSC - CMMC - DistBaseline - EDR<br/>GDPR - HIPAA - PCI - SOC2"]
    end

    subgraph OUT ["Outputs"]
        direction LR
        HTML["HTML report<br/>(interactive)"]
        JSON["JSON"]
        CSV["CSV"]
        XML["XML"]
        CONSOLE["Console"]
    end

    CLI --> SC
    CLI --> MOD
    SC --> MOD
    MOD --> CLI
    CLI --> OUT
Loading

Module Execution Lifecycle

Each module's execution follows a deterministic lifecycle. The sequence below shows what happens when the orchestrator invokes a single framework module:

sequenceDiagram
    participant Orch as Orchestrator
    participant SC as SharedDataCache
    participant OS as os_detection
    participant Mod as module_X
    participant RL as remediation_library

    Orch->>OS: detect_os()
    OS-->>Orch: OSInfo (cached)
    Orch->>Orch: Build shared_data dict<br/>(hostname, os_version, os_info,<br/>scan_date, cache, ...)

    Orch->>Mod: run_checks(shared_data)
    activate Mod

    Mod->>OS: detect_os() (cached, ~free)
    OS-->>Mod: OSInfo

    loop For each check
        Mod->>SC: get(/etc/file)
        SC->>SC: cache hit?
        alt cache hit
            SC-->>Mod: cached content
        else cache miss
            SC->>SC: read file
            SC-->>Mod: content
        end
        Mod->>Mod: evaluate
        opt fail or warn
            Mod->>RL: remediation_for(tool_id)
            RL-->>Mod: distro-aware text
        end
        Mod->>Mod: append AuditResult
    end

    Mod-->>Orch: List[AuditResult]
    deactivate Mod

    Orch->>Orch: aggregate, run pipeline phases
    Orch->>Orch: render reports (HTML, JSON, ...)
Loading

The cache is shared across all modules in a single audit run. Filesystem reads, command executions, and OS detection results are deduplicated automatically - yielding ~50% cache hit rates in production audits.


Cache Hierarchy

flowchart LR
    Modules[All 16 modules] --> Cache[(SharedDataCache)]

    subgraph Cache_Internals ["SharedDataCache contents"]
        FC["File contents<br/>(/etc/*, /proc/*)"]
        CMD["Command outputs<br/>(systemctl, ip, etc.)"]
        SVC["Service states<br/>(active/inactive)"]
        PKG["Package presence<br/>(dpkg, rpm)"]
        OS_C["OSInfo<br/>(detected once)"]
    end

    Cache --> FC
    Cache --> CMD
    Cache --> SVC
    Cache --> PKG
    Cache --> OS_C

    Cache -. invalidated at start .-> Reset((New audit))
Loading

Cache scope is per-audit-run. The cache is created in the orchestrator and passed down through shared_data["cache"]. Modules that opt into caching call helpers like read_file_safe() and command_exists() from audit_common, which transparently consult the cache.


Remediation Library Architecture

flowchart TB
    Module[Module check site] --> Helper[remediation_for tool_id]
    Helper --> OS[detect_os cached]
    OS -.OSInfo.-> Helper
    Helper --> RL[remediation_library.get_remediation]
    RL --> RD{Tool registered?}
    RD -->|yes| Resolve[Resolve family-specific:<br/>primary_packages,<br/>supporting_packages,<br/>post_install,<br/>services,<br/>verify]
    RD -->|no| Fallback["return None<br/>(caller falls back to<br/>its own short string)"]
    Resolve --> Compose[Compose multi-line text:<br/>Install / Configure /<br/>Enable / Verify / Notes / See]
    Compose --> Result["AuditResult.remediation =<br/>distro-aware multi-step guidance"]
    Fallback --> Result
Loading

Example flow for a Debian system, AIDE missing:

  1. Module detects no AIDE database file present
  2. Calls remediation_for("aide")
  3. Helper resolves cached OSInfo (family=Debian)
  4. Library returns:
    • Install: apt-get install -y aide aide-common (includes supporting aide-common)
    • Configure: aideinit; cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db; aide --check
    • Verify: test -f /var/lib/aide/aide.db && echo 'AIDE database initialized'
    • Notes about scheduling and SIEM forwarding
    • References to upstream documentation

v3.0 Foundation Library

The v3.0 release introduced a foundation library of shared components alongside the existing audit_common.py; later releases (v3.6-v3.9) added several more. Each is designed to be importable and useful in isolation while wiring into the integrated pipeline through v3_pipeline.py. The full current set:

Module Reference

File Purpose Key Public Symbols
audit_common.py Core result object, OS info, safe command/file helpers AuditResult, OSInfo, run_command(), safe_int_parse()
correlation_registry.py Cross-framework control mappings (158 topics) get_correlations(), enrich(), register_correlation()
os_detection.py Distribution/family/kernel detection detect_os(), OSInfo, KernelVersion, family constants
risk_scoring.py 1-100 risk priority scoring compute_risk_score(), RiskScore, classify_*()
baseline_compare.py Drift detection between audit runs compare_to_baseline(), DriftReport, FindingDelta
remediation_bundles.py Predefined remediation groupings list_bundles(), get_bundle(), Bundle, ImpactProfile
rollback_generator.py Inverse change scripts RollbackGenerator, CaptureRecord
v3_pipeline.py High-level audit pipeline AuditPipeline, compute_compliance_scores(), export_json_v3()
module_helpers.py (v3.6) Per-process helper cache command_available(), systemd_active(), run_command(), read_sysctl(), read_file_safe(), file_exists(), directory_exists(), clear_caches()
host_facts.py (v3.7) Cross-module fact computation (75 fields) HostFacts, compute_host_facts()
profiles.py (v3.7; 18 profiles in v3.9) Per-distribution result filtering list_profiles(), get_profile(), apply_profile(), filter_results(), validate_profile_name()
attack_surface.py (v3.8) Attack-surface assessment + HTML report build_attack_surface(), render_attack_surface_html(), AttackSurface, DomainAssessment
shared_assessments.py (v3.9) Canonical physical assessments get_world_writable_assessment(), get_suid_sgid_assessment(), get_unowned_files_assessment(), get_firewall_posture()
canonical_remediations.py (v3.9) Cross-framework remediation registry classify_topic(), canonical_for(), normalize_remediation(), is_value_independent(), all_topics(), topic_index()

Result Object: 9-Field AuditResult

Every check produces an AuditResult with these fields:

@dataclass(slots=True)
class AuditResult:
    module: str                            # "CORE", "STIG", "CIS", etc.
    category: str                          # "CIS 5.2 - SSH"
    status: str                            # Pass/Fail/Warning/Info/Error
    message: str                           # One-line check identifier
    details: str                           # Multi-line technical detail
    remediation: str                       # Shell command or guidance
    severity: str                          # Critical/High/Medium/Low/Informational
    cross_references: Dict[str, str]       # Framework -> control_id mapping
    timestamp: str                         # ISO 8601 capture time

The orchestrator's pipeline enriches cross_references automatically via the correlation registry, so module authors only need to populate the field for module-specific topics not in the registry.


OS Detection Architecture

Detection runs once at orchestrator startup and is cached for the duration of the audit. Multiple sources are consulted in priority order:

  1. /etc/os-release - systemd standard, present on essentially all modern distributions
  2. /usr/lib/os-release - immutable systems
  3. /etc/lsb-release - LSB standard, common on Ubuntu derivatives
  4. /etc/redhat-release and family-specific marker files
  5. Fallback marker files (/etc/debian_version, /etc/alpine-release, etc.)
  6. uname-derived fallback (last resort)
flowchart TD
    Start([detect_os called]) --> Check1{/etc/os-release<br/>exists?}
    Check1 -->|yes| Parse1[Parse key=value<br/>extract ID, NAME,<br/>VERSION_ID, ID_LIKE,<br/>VERSION_CODENAME,<br/>PRETTY_NAME]
    Check1 -->|no| Check2{/usr/lib/os-release<br/>exists?}
    Check2 -->|yes| Parse1
    Check2 -->|no| Check3{/etc/lsb-release<br/>exists?}
    Check3 -->|yes| Parse2[Parse DISTRIB_ID,<br/>DISTRIB_RELEASE,<br/>DISTRIB_CODENAME,<br/>DISTRIB_DESCRIPTION]
    Check3 -->|no| Check4{Marker file<br/>present?}
    Check4 -->|yes| Parse3[Identify by<br/>marker filename<br/>+ extract version<br/>from file content]
    Check4 -->|no| Fallback["pretty_name =<br/>'Linux X.Y.Z (unidentified)'"]

    Parse1 --> Classify[_classify_family<br/>uses ID + ID_LIKE]
    Parse2 --> Classify
    Parse3 --> Classify

    Classify --> Family{Family<br/>determined?}
    Family -->|Debian| FD[FAMILY_DEBIAN<br/>pkg=apt]
    Family -->|RedHat| FR[FAMILY_REDHAT<br/>pkg=dnf/yum]
    Family -->|SUSE| FS[FAMILY_SUSE<br/>pkg=zypper]
    Family -->|Arch| FA[FAMILY_ARCH<br/>pkg=pacman]
    Family -->|Alpine| FAL[FAMILY_ALPINE<br/>pkg=apk]

    FD --> Final[Detect kernel,<br/>MAC framework,<br/>firewall, container,<br/>cloud provider]
    FR --> Final
    FS --> Final
    FA --> Final
    FAL --> Final
    Fallback --> Final
    Final --> Cache[Cache OSInfo<br/>return to caller]
Loading

The detected OSInfo object exposes:

  • distro_id - lowercase identifier matching os-release ID (e.g. "ubuntu", "rhel", "rocky")
  • distro_name - human-readable name
  • pretty_name - full PRETTY_NAME from os-release (e.g. "Ubuntu 24.04.4 LTS")
  • version_id - version string
  • version_codename - distribution codename (e.g. "jammy", "bookworm")
  • family - canonical family code (Debian/RedHat/SUSE/Arch/Alpine/Gentoo/Slackware/Void/NixOS/Unknown)
  • package_manager - detected canonical command for this distro
  • init_system - systemd/openrc/runit/s6/dinit/sysvinit
  • mac_framework - selinux/apparmor/tomoyo/smack/yama
  • firewall - firewalld/ufw/nftables/iptables/shorewall
  • container - docker/kubernetes/lxc/podman/systemd-nspawn (if running inside one)
  • cloud_provider - aws/azure/gcp/digitalocean/linode/etc. (DMI inspection only, no network)
  • architecture - x86_64/aarch64/armv7l/etc.
  • kernel - parsed KernelVersion with at_least(major, minor, patch) helper
  • eol - true if version is past end-of-life per bundled data

Modules should branch on family for broad logic (Debian-family vs Red Hat-family) and only branch on distro_id for distribution-specific quirks.

Supported Distributions

Debian family: Debian, Ubuntu, Linux Mint, Pop!_OS, elementary OS, Kali Linux, Zorin OS, MX Linux, Deepin, Parrot OS, Tails, Raspberry Pi OS, Devuan, KDE neon, Lubuntu, Xubuntu, Kubuntu, Ubuntu MATE, Ubuntu Budgie, antiX, siduction, BunsenLabs, Endless OS, LXLE, Peppermint, Qubes OS

Red Hat family: RHEL, CentOS, CentOS Stream, Fedora, Rocky Linux, AlmaLinux, Oracle Linux, Amazon Linux 2/2023, Scientific Linux, ClearOS, Springdale Linux, Circle Linux, Navy Linux, EuroLinux, MIRACLE LINUX, OpenELA

SUSE family: openSUSE Leap, openSUSE Tumbleweed, SUSE Linux Enterprise Server (SLES), SUSE Linux Enterprise Desktop (SLED), SLE HPC, GeckoLinux

Arch family: Arch Linux, Manjaro, EndeavourOS, Garuda Linux, ArcoLinux, Artix Linux, BlackArch, CachyOS, RebornOS, Obarun, Parabola

Independent: Alpine, Gentoo, Calculate Linux, Funtoo, Slackware, Salix, Void Linux, NixOS


Cross-Framework Correlation Registry

The registry maps logical control topics (e.g. "ssh.permit_root_login") to their framework-specific identifiers (CIS 5.2.7, NIST AC-6(2), STIG V-230296, etc.). The current registry contains 158 topics covering the most common Linux security checks.

Registry Schema

("ssh.permit_root_login", {
    "CIS":      "5.2.7",
    "NIST":     "AC-6(2)",
    "STIG":     "V-230296",
    "ISO27001": "A.8.2",
    "NSA":      "SSH-1.3",
    "CISA":     "CPG-2.E",
    "PCI-DSS":  "8.2.1",
})

Enrichment Flow

When the orchestrator's pipeline runs, each result is enriched in this order:

  1. Module-supplied cross-references take highest precedence
  2. Heuristic resolver scans message, details, and category for keywords (e.g. "permitrootlogin" matches ssh.permit_root_login)
  3. Registry lookup returns the full mapping for the matched topic
  4. Merge preserves module-supplied values; only fills in framework slots that the module didn't populate

This means a module can provide partial cross-references (e.g. just CIS) and let the registry fill in the others.

Sources

Mappings are sourced from publicly published official documentation:

  • CIS Benchmarks for Linux v3.0.0 (Distribution Independent)
  • NIST SP 800-53 Rev 5 control catalog
  • ISO/IEC 27001:2022 Annex A
  • DISA STIG for RHEL 9 V1R6, Ubuntu 22.04 V1R3
  • PCI DSS v4.0.1
  • HIPAA Security Rule Sec. 164.312
  • GDPR Article 32
  • NSA Network Infrastructure Security Guide
  • CISA Cybersecurity Performance Goals v1.0.1
  • ENISA Baseline Security Recommendations

Risk Priority Scoring

The scoring engine produces a 1-100 risk priority for each Fail/Warning finding by combining four weighted components:

Component Weight Values
Severity 40% Critical=40, High=30, Medium=20, Low=10, Informational=5
Exploitability 25% KnownExploited=25, Remote=20, Local=12, Physical=4, NotExploitable=0
Exposure 20% InternetFacing=20, DMZ=15, Internal=10, Isolated=5
Asset Criticality 15% User-supplied 1-10, scaled to 0-15

The orchestrator infers exploitability from the finding text (KEV catalog references, service keywords, kernel keywords) and exposure from the OS detection result (cloud provider, container runtime, listening ports). Operators can override via --asset-criticality.

Example

A Critical SSH PermitRootLogin finding on an internet-facing AWS host with criticality 9 yields:

Severity component:    40 (Critical)
Exploitability:        20 (Remote - SSH keyword)
Exposure:              20 (InternetFacing - AWS detected)
Criticality:           14 (9/10 * 15)
                      ----
Total:                 94 / 100

This same finding on an isolated build server with criticality 3 yields:

Severity component:    40 (Critical)
Exploitability:        20 (Remote)
Exposure:               5 (Isolated)
Criticality:            5 (3/10 * 15)
                      ----
Total:                 70 / 100

The 24-point spread reflects the contextual difference even though severity is identical.


Audit Pipeline

The pipeline runs after all modules have executed and produced their AuditResult lists. It applies five phases in fixed order:

flowchart LR
    Modules["Modules<br/>(raw AuditResult lists)"]
    P1["1. Validation<br/>Normalise statuses,<br/>severities, sanitise text"]
    P2["2. Correlation<br/>Apply cross-framework<br/>registry enrichment"]
    P3["3. Risk Scoring<br/>Compute 1-100 priority<br/>for each Fail/Warning"]
    P4["4. Compliance Scoring<br/>Simple, weighted,<br/>severity-adjusted<br/>per-module and overall"]
    P5["5. Baseline Diff<br/>Optional drift compare<br/>if --baseline supplied"]
    Final[(PipelineResult)]

    Modules --> P1 --> P2 --> P3 --> P4 --> P5 --> Final

    Final --> RHTML[HTML]
    Final --> RJSON[JSON]
    Final --> RCSV[CSV]
    Final --> RXML[XML]
    Final --> RConsole[Console]

    style Modules fill:#e1f5ff
    style Final fill:#fff4e1
Loading

Each phase is idempotent and side-effect-free. The result of each phase is consumable by the report generators.


Compliance Scoring (3 methods)

All three scores are computed in ComplianceScore.calculate() in linux_security_audit.py. The applicable denominator excludes Informational results (they are advisory, not pass/fail): applicable = total_checks - info.

Simple Score

simple_pct = passed / applicable * 100

Straight pass rate over applicable (non-Info) checks. If there are no applicable checks, the score is 100.0.

Weighted Score

weighted_pct = (passed * 1.0 + warnings * 0.5) / applicable * 100

Status-weighted (not severity-weighted): a Pass earns full credit, a Warning earns half credit, and Fail/Error earn zero. This is the score compared against --threshold to produce the PASS/FAIL threshold result.

Severity-Adjusted Score

This method scales the failure rate by how much of the weight is concentrated in Critical/High findings, so a system that fails high-severity checks scores lower than one that fails only low-severity checks:

severity_weights   = {Critical: 5.0, High: 3.0, Medium: 1.5, Low: 0.5, Informational: 0.0}
total_weight       = sum(severity_weights[sev] * count) over non-Info severities
fail_rate          = (failed + errors) / applicable
crit_high_weight   = 5.0 * critical_count + 3.0 * high_count
severity_factor    = 1.0 + (crit_high_weight / total_weight)
adjusted_fail_rate = min(1.0, fail_rate * severity_factor)
severity_weighted_pct = (1.0 - adjusted_fail_rate) * 100   # clamped to [0, 100]

If no severity distribution is available it falls back to weighted_pct.

Note: a separate, coarser integer weighting (_SEVERITY_WEIGHTS = {Critical: 40, High: 30, Medium: 20, Low: 10, Informational: 5}) lives in shared_components/risk_scoring.py and is used only for risk prioritization/ranking of individual findings, not for these compliance percentages.

Threshold Result

threshold_result = "PASS" if weighted_pct >= threshold else "FAIL"   # default threshold 70.0

Remediation Bundles

Bundles group related correlation topics so an operator can apply a coherent set of fixes with a single command:

Bundle Topics SSH Continuity Risk Reboot Required
HardenSSH 14 Yes No
DisableLegacyProtocols 3 No No
HardenKernel 18 No No
EnableAuditLogging 18 No Yes
HardenAuthentication 12 No No
LockDownNetwork 5 Yes No
SecureBootChain 3 No Yes
HardenSystemd 3 No No

The orchestrator resolves a bundle name to its included topics, then to the specific findings in the current audit that match those topics, and applies their existing remediation commands. Bundles do not duplicate remediation logic; they're metadata describing which checks belong together.


Rollback Script Generation

When remediation runs, the orchestrator can capture pre-modification state and generate an inverse bash script. Capture types:

  • File content + permissions - Base64-encoded snapshot for full restoration
  • Sysctl values - Restore previous integer or remove drop-in line
  • Service state - systemctl start/stop to restore previous active state
  • Service enablement - systemctl enable/disable/mask to restore boot behaviour
  • Kernel module loading - modprobe or modprobe -r based on previous state

Generated scripts use set -euo pipefail, validate root privilege at start, log each operation, and process records in reverse order (LIFO). Output files are written 0700 (root-only) via atomic write-rename.


Threading Model

  • Module execution can run in parallel via --parallel --workers N. Each module runs in its own thread with the shared cache (read-only after warm-up).
  • Pipeline phases run sequentially in the main thread. Phase 1 (validation) and Phase 2 (correlation) iterate over results and produce new lists.
  • Foundation module caches (correlation registry, OS detection, etc.) are guarded by module-level threading.Lock() for thread-safe access during parallel module execution.

File I/O Safety

All output files are written with explicit safety properties:

  • Path traversal validation - os.path.abspath() resolution, parent directory existence check, traversal pattern rejection
  • Atomic writes - write-to-temp-then-rename pattern; partial files never visible to consumers
  • Permission setting - Reports written 0o600 (owner-only), logs written 0o644, rollback scripts written 0o700 (root-only executable)
  • Encoding - UTF-8 with errors="replace" for tolerance of malformed system file content

See Also


<- Back to Home

Linux Security Audit

Version 3.9 - 16 modules - 2,297 checks


Getting Started


Reference


Architecture


Operations


Release Information


Quick Reference

Original modules (v2.0 baseline + v3.3 expansion)

Core - CIS - CISA - ENISA - ISO 27001 - NIST - NSA - STIG

New modules (v3.0+ Phase 3)

ACSC - CMMC - DistBaseline - EDR - GDPR - HIPAA - PCI-DSS - SOC2

Output Formats

HTML - JSON - CSV - XML - Console

Status Values

Pass - Fail - Warning - Info - Error

Severity Levels

Critical - High - Medium - Low - Informational


External Links

Clone this wiki locally