-
Notifications
You must be signed in to change notification settings - Fork 2
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).
The system is organised into three layers:
-
Modules (
modules/) - One file per security framework. Each module performs its own checks, returning a list ofAuditResultobjects. -
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), andcanonical_remediations.py(v3.9). -
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["Core - CIS - CISA - ENISA<br/>ISO27001 - NIST - NSA - STIG"]
NEW["ACSC - CMMC - DistBaseline - EDR<br/>GDPR - HIPAA - PCI-DSS - 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
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, ...)
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.
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))
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.
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
Example flow for a Debian system, AIDE missing:
- Module detects no AIDE database file present
- Calls
remediation_for("aide") - Helper resolves cached
OSInfo(family=Debian) - Library returns:
- Install:
apt-get install -y aide aide-common(includes supportingaide-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
- Install:
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:
| 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()
|
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 timeThe 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.
Detection runs once at orchestrator startup and is cached for the duration of the audit. Multiple sources are consulted in priority order:
-
/etc/os-release- systemd standard, present on essentially all modern distributions -
/usr/lib/os-release- immutable systems -
/etc/lsb-release- LSB standard, common on Ubuntu derivatives -
/etc/redhat-releaseand family-specific marker files - Fallback marker files (
/etc/debian_version,/etc/alpine-release, etc.) - 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]
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
KernelVersionwithat_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.
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
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.
("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",
})When the orchestrator's pipeline runs, each result is enriched in this order:
- Module-supplied cross-references take highest precedence
-
Heuristic resolver scans
message,details, andcategoryfor keywords (e.g. "permitrootlogin" matchesssh.permit_root_login) - Registry lookup returns the full mapping for the matched topic
- 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.
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
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.
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.
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
Each phase is idempotent and side-effect-free. The result of each phase is consumable by the report generators.
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_pct = passed / applicable * 100
Straight pass rate over applicable (non-Info) checks. If there are no applicable checks, the score is 100.0.
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.
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 inshared_components/risk_scoring.pyand is used only for risk prioritization/ranking of individual findings, not for these compliance percentages.
threshold_result = "PASS" if weighted_pct >= threshold else "FAIL" # default threshold 70.0
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.
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/stopto restore previous active state -
Service enablement -
systemctl enable/disable/maskto restore boot behaviour -
Kernel module loading -
modprobeormodprobe -rbased 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.
-
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.
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
- Development Guide - How to extend modules with new checks
- Output Reference - Report format specifications
- Framework Reference - Framework-specific details
Linux Security Audit Project - Version 3.9 - MIT License
Repository - Releases - Issues - Pull Requests
Changelog - Contributing - Security Policy - License
Frameworks: Core - CIS - CISA - ENISA - ISO 27001 - NIST - NSA - STIG - ACSC - CMMC - DistBaseline - EDR - GDPR - HIPAA - PCI-DSS - SOC2
Coverage: 16 Modules - 2,297 Automated Security Checks - 5 Native Output Formats - Zero External Dependencies
This documentation reflects Linux Security Audit Project v3.9 (cross-framework remediation consistency via the canonical remediation registry, PCI-DSS module rename, compliance-scoring fix, attack-surface assessment, per-framework split reports, cross-module correlation, 18 distribution profiles, rollup metrics, OS-aware remediation library). For older versions, see the release tags.
Version 3.9 - 16 modules - 2,297 checks
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