A CLI-first infrastructure security scanner that runs KISA-based hardening checks over SSH and reports PASS / FAIL / MANUAL per rule.
BluePy is an early alpha. It ships only the checks whose logic has been causally verified with branch-covered tests against real command output. Everything else from the legacy KISA rule corpus is treated as experimental and is excluded from the default scan because its YAML→validator mappings are unreliable (some legacy validators returned unconditional PASS or could never PASS). Shipping those as a "133-rule scanner" would give a false sense of safety, so we don't.
What this means concretely:
- Default scan = verified checks only (a small set, see the table below).
- Unverified checks carry
experimental: truein their rule YAML and only run with--include-experimental(clearly labelled, not trusted). - The known false-PASS validators (
check_u02,check_u06,check_w03) were deleted, not hidden. - Windows transport (WinRM) exists in the codebase but is release-hidden for v0.1 (no verified Windows checks; no Windows live-test fixture yet).
| Track | Transport | v0.1 status |
|---|---|---|
| Linux | SSH (asyncssh) | active — verified subset exposed |
| macOS | SSH (asyncssh) | active — verified subset exposed |
| Windows | WinRM (pywinrm) | engine present, release-hidden / deferred |
"Verified" means the validator has PASS + FAIL + MANUAL branch-covered causal tests with anchored matchers, exercised against real command output.
| Rule | Check | Command | Proof level |
|---|---|---|---|
| U-18 | /etc/passwd owner/permission |
ls -l /etc/passwd |
Linux live e2e (Docker fixture, real SSH) |
| U-19 | /etc/shadow owner/permission |
ls -l /etc/shadow |
Linux live e2e (Docker fixture, real SSH) |
| M-01 | System Integrity Protection (SIP) | csrutil status |
macOS validator causal tests + 1 local capture |
| M-02 | FileVault full-disk encryption | fdesetup status |
macOS validator causal tests + 1 local capture |
| M-05 | Automatic updates | defaults read … AutomaticCheckEnabled |
macOS validator causal tests |
Proof levels differ by track and we state that honestly: the Linux verified checks are proven by a live end-to-end scan (real SSH against a Docker fixture); the macOS checks are proven by branch-covered validator causal tests plus a one-time real local command capture, with live SSH confirmation performed manually (CI runners have no macOS+Remote Login target).
Everything not in this table is experimental and hidden unless you pass
--include-experimental.
BluePy uses uv and is pinned to Python 3.12
(.python-version). The default install pulls only the minimal scan-path
dependencies (pydantic, pyyaml, asyncssh).
# 1) install uv (if needed)
curl -LsSf https://astral.sh/uv/install.sh | sh # or: pip install uv
# 2) install Python 3.12 + minimal scan-path deps
uv syncRun from the repository root with uv run — the validators do import src.*
and the rules path is relative (config/rules), so the scanner runs in-place.
There is no installable wheel or standalone binary in v0.1.
# scan a remote Linux server (verified checks only), human-readable table
uv run python -m src.cli.commands scan --host <HOST> --user <USER> --password <PW> --platform linux
# machine-readable counts
uv run python -m src.cli.commands scan --host <HOST> --user <USER> --password <PW> --platform linux --json
# include unverified (experimental) checks too
uv run python -m src.cli.commands scan --host <HOST> --user <USER> --key <KEYFILE> --include-experimentalbluepy scan --host HOST --user USER (--password PW | --key KEYFILE)
[--port N] [--rules-dir config/rules]
[--platform linux|macos|windows] [--server-id ID]
[--format table|plain] [--json]
[--include-experimental] [--insecure]
- Authentication: pass exactly one of
--password/--key(key wins if both). --platform: linux/macos use SSH; windows uses WinRM (uv sync --extra windows).--format table(default) prints a per-rule table;--format plainprints a one-line summary.--jsonalways emits the frozen counts payload below.--include-experimental: also run unverified checks (default: verified-only).--insecure: disable SSH host-key verification (MITM risk; see Security).
{"server_id":"...","platform":"linux","total":2,"passed":1,"failed":1,"manual":0,"score":50.0}The security posture (how many checks FAILED) lives in the JSON counts, never in the exit code.
| Code | Meaning |
|---|---|
| 0 | scan completed |
| 2 | connection failure |
| 3 | rules load failure |
| 4 | usage / argument / auth error |
| 1 | unexpected runtime error |
The default install is the minimal scan path. Everything else is optional:
uv sync --extra gui # PySide6 GUI (deferred / unsupported in v0.1)
uv sync --extra report # openpyxl / reportlab / jinja2
uv sync --extra db # sqlalchemy / alembic
uv sync --extra windows # pywinrm (Windows targets)
uv sync --extra full # everythingBluePy is a security tool, so it must not undermine its own transport:
- SSH host-key verification is strict by default. A first connection to an
unknown host fails unless its key is in your
known_hosts. --insecuredisables host-key verification for that run and prints a warning. Use it only for throwaway lab targets; it exposes you to MITM. It is not used by the live test harness (the harness seeds the fixture host key and connects on the strict path).
To report a vulnerability, see SECURITY.md.
# unit + integration tests (Docker-free)
uv run pytest tests/unit tests/integration
# Docker-free causal/logic tests for the acceptance harness + verified set
uv run pytest tests/acceptance/test_acceptance_logic.py
# live end-to-end acceptance (real SSH against a committed Docker fixture)
uv run python tests/acceptance/run_acceptance.pyThe live harness in tests/acceptance/run_acceptance.py brings up a committed
fixture (seeded so /etc/shadow is 400 → U-19 PASS and /etc/passwd is 644 →
U-18 FAIL), then proves a real config-driven scan and binds the CLI --json
counts to the in-process result. It needs a Docker daemon: locally on Apple
Silicon use colima (brew install colima docker docker-compose && colima start); in CI the native Docker on the Linux runner is used (the harness is
runner-agnostic — colima is only the local path).
Rule definitions derive from the KISA infrastructure security guide (https://www.kisa.or.kr), cross-referenced with the CIS Benchmarks. The legacy 2017 rule corpus was migrated mechanically and many mappings are unreliable; v0.1 only vouches for the verified set above and exposes the rest as experimental.
- Windows track exposure (engine present, release-hidden).
- TUI front-end (planned v0.2).
- PySide6 GUI.
- Remediation / auto-fix exposure (engine present, not exposed).
- Full repair of the scrambled legacy KISA mappings.
See CONTRIBUTING.md — including how a check graduates from
experimental to the verified set (branch-covered causal tests + anchored
matchers).
MIT.