Skip to content

scan_cmd

scan_cmd

jarvis scan — audit your environment for privacy and security risks.

Classes

ScanResult dataclass

ScanResult(name: str, status: str, message: str, platform: str)

Result of a single privacy/security check.

PrivacyScanner

Collection of environment privacy checks.

Functions
check_filevault
check_filevault() -> ScanResult

Check whether FileVault disk encryption is enabled (macOS).

Source code in src/openjarvis/cli/scan_cmd.py
def check_filevault(self) -> ScanResult:
    """Check whether FileVault disk encryption is enabled (macOS)."""
    try:
        proc = self._run(["fdesetup", "status"])
        if "On" in proc.stdout:
            return ScanResult(
                name="FileVault",
                status="ok",
                message="FileVault is enabled.",
                platform="darwin",
            )
        return ScanResult(
            name="FileVault",
            status="fail",
            message="FileVault is NOT enabled. Enable full-disk encryption.",
            platform="darwin",
        )
    except Exception:
        return ScanResult(
            name="FileVault",
            status="skip",
            message="fdesetup not available.",
            platform="darwin",
        )
check_mdm
check_mdm() -> ScanResult

Check whether the device is enrolled in an MDM profile (macOS).

Source code in src/openjarvis/cli/scan_cmd.py
def check_mdm(self) -> ScanResult:
    """Check whether the device is enrolled in an MDM profile (macOS)."""
    try:
        proc = self._run(["profiles", "status", "-type", "enrollment"])
        output = proc.stdout + proc.stderr
        lower = output.lower()
        # "not enrolled" is a strong negative signal — check it first.
        not_enrolled = "not enrolled" in lower or "no" in lower
        # Positive signals: explicit Yes/enrolled without a negation.
        enrolled_yes = (
            "mdm enrollment: yes" in lower
            or "enrolled via dep: yes" in lower
            or ("enrolled" in lower and not not_enrolled)
        )
        if enrolled_yes:
            return ScanResult(
                name="MDM Enrollment",
                status="warn",
                message="Device appears to be enrolled in an MDM profile.",
                platform="darwin",
            )
        return ScanResult(
            name="MDM Enrollment",
            status="ok",
            message="Device is not enrolled in an MDM profile.",
            platform="darwin",
        )
    except Exception:
        return ScanResult(
            name="MDM Enrollment",
            status="skip",
            message="profiles command not available.",
            platform="darwin",
        )
check_icloud_sync
check_icloud_sync() -> ScanResult

Check whether ~/.openjarvis is inside iCloud Drive sync scope.

Source code in src/openjarvis/cli/scan_cmd.py
def check_icloud_sync(self) -> ScanResult:
    """Check whether ~/.openjarvis is inside iCloud Drive sync scope."""
    try:
        config_path = Path("~/.openjarvis").expanduser().resolve()
        icloud_path = Path("~/Library/Mobile Documents/").expanduser().resolve()
        if str(config_path).startswith(str(icloud_path)):
            return ScanResult(
                name="iCloud Sync",
                status="warn",
                message="~/.openjarvis may be synced to iCloud.",
                platform="darwin",
            )
        # Also probe defaults for com.apple.bird (iCloud daemon)
        try:
            proc = self._run(
                ["defaults", "read", "com.apple.bird", "optout_preference"]
            )
            val = proc.stdout.strip()
            if val == "0":
                return ScanResult(
                    name="iCloud Sync",
                    status="warn",
                    message="iCloud Desktop/Documents sync may be active.",
                    platform="darwin",
                )
        except Exception:
            pass
        return ScanResult(
            name="iCloud Sync",
            status="ok",
            message="~/.openjarvis is not inside iCloud Drive.",
            platform="darwin",
        )
    except Exception:
        return ScanResult(
            name="iCloud Sync",
            status="skip",
            message="Could not determine iCloud sync status.",
            platform="darwin",
        )
check_luks
check_luks() -> ScanResult

Check whether any block device uses LUKS encryption (Linux).

Source code in src/openjarvis/cli/scan_cmd.py
def check_luks(self) -> ScanResult:
    """Check whether any block device uses LUKS encryption (Linux)."""
    try:
        proc = self._run(["lsblk", "-o", "NAME,TYPE,FSTYPE", "-J"])
        data = json.loads(proc.stdout)
    except Exception:
        return ScanResult(
            name="LUKS Encryption",
            status="skip",
            message="lsblk not available or returned unexpected output.",
            platform="linux",
        )

    def _has_luks(devices: list) -> bool:  # type: ignore[type-arg]
        for dev in devices:
            if dev.get("fstype") == "crypto_LUKS":
                return True
            children = dev.get("children") or []
            if _has_luks(children):
                return True
        return False

    try:
        devices = data.get("blockdevices", [])
        if _has_luks(devices):
            return ScanResult(
                name="LUKS Encryption",
                status="ok",
                message="At least one LUKS-encrypted device found.",
                platform="linux",
            )
        return ScanResult(
            name="LUKS Encryption",
            status="fail",
            message="No LUKS-encrypted block devices found.",
            platform="linux",
        )
    except Exception:
        return ScanResult(
            name="LUKS Encryption",
            status="skip",
            message="Could not parse lsblk output.",
            platform="linux",
        )
check_cloud_sync_agents
check_cloud_sync_agents() -> ScanResult

Check for running cloud-sync agent processes.

Source code in src/openjarvis/cli/scan_cmd.py
def check_cloud_sync_agents(self) -> ScanResult:
    """Check for running cloud-sync agent processes."""
    return self._check_processes(
        names=_CLOUD_SYNC_PROCS,
        check_name="Cloud Sync Agents",
        warn_msg="{name} sync agent is running — weights may be uploaded to cloud.",
        platform="all",
    )
check_network_exposure
check_network_exposure() -> ScanResult

Check if engine ports are exposed on 0.0.0.0 rather than localhost.

Source code in src/openjarvis/cli/scan_cmd.py
def check_network_exposure(self) -> ScanResult:
    """Check if engine ports are exposed on 0.0.0.0 rather than localhost."""
    try:
        if sys.platform == "darwin":
            proc = self._run(["lsof", "-iTCP", "-sTCP:LISTEN", "-n", "-P"])
        else:
            proc = self._run(["ss", "-tlnp"])
        output = proc.stdout

        exposed: list[int] = []
        for port in _ENGINE_PORTS:
            # Look for patterns like *:PORT or 0.0.0.0:PORT
            for token in (f"*:{port}", f"0.0.0.0:{port}", f":::{port}"):
                if token.replace(" ", "") in output.replace(" ", ""):
                    exposed.append(port)
                    break

        if exposed:
            ports_str = ", ".join(str(p) for p in sorted(exposed))
            return ScanResult(
                name="Network Exposure",
                status="warn",
                message=f"Engine port(s) {ports_str} exposed on all interfaces.",
                platform="all",
            )
        return ScanResult(
            name="Network Exposure",
            status="ok",
            message="All engine ports appear to be bound to localhost only.",
            platform="all",
        )
    except Exception:
        return ScanResult(
            name="Network Exposure",
            status="skip",
            message="Could not determine network exposure (lsof/ss unavailable).",
            platform="all",
        )
check_screen_recording
check_screen_recording() -> ScanResult

Check for running screen-recording / remote-desktop processes (macOS).

Source code in src/openjarvis/cli/scan_cmd.py
def check_screen_recording(self) -> ScanResult:
    """Check for running screen-recording / remote-desktop processes (macOS)."""
    return self._check_processes(
        names=_SCREEN_RECORDING_PROCS,
        check_name="Screen Recording",
        warn_msg="{name} is running — screen may be accessible remotely.",
        platform="darwin",
    )
check_remote_access
check_remote_access() -> ScanResult

Check for running remote-access / tunneling processes.

Source code in src/openjarvis/cli/scan_cmd.py
def check_remote_access(self) -> ScanResult:
    """Check for running remote-access / tunneling processes."""
    return self._check_processes(
        names=_REMOTE_ACCESS_PROCS,
        check_name="Remote Access",
        warn_msg="{name} is running — system may be accessible remotely.",
        platform="all",
    )
check_dns
check_dns() -> ScanResult

Check DNS configuration for encrypted resolvers (macOS).

Source code in src/openjarvis/cli/scan_cmd.py
def check_dns(self) -> ScanResult:
    """Check DNS configuration for encrypted resolvers (macOS)."""
    if sys.platform != "darwin":
        return ScanResult(
            name="DNS Configuration",
            status="skip",
            message="DNS check not yet implemented for this platform.",
            platform="darwin",
        )
    try:
        proc = self._run(["scutil", "--dns"])
        output = proc.stdout
    except Exception:
        return ScanResult(
            name="DNS Configuration",
            status="skip",
            message="scutil command not available.",
            platform="darwin",
        )

    _DOH_INDICATORS = [
        "dns-over-https",
        "dns-over-tls",
        "encrypted",
        "doh",
        "dot",
    ]
    if any(ind in output.lower() for ind in _DOH_INDICATORS):
        return ScanResult(
            name="DNS Configuration",
            status="ok",
            message="Encrypted DNS (DoH/DoT) appears to be active.",
            platform="darwin",
        )

    # Extract nameserver IPs
    nameservers: list[str] = []
    for line in output.splitlines():
        stripped = line.strip()
        if stripped.startswith("nameserver["):
            parts = stripped.split(":", 1)
            if len(parts) == 2:
                nameservers.append(parts[1].strip())

    if not nameservers:
        return ScanResult(
            name="DNS Configuration",
            status="ok",
            message="Could not parse DNS nameservers from scutil output.",
            platform="darwin",
        )

    _PLAIN_DNS = {"8.8.8.8", "8.8.4.4", "1.1.1.1", "1.0.0.1", "9.9.9.9"}
    plain = [ns for ns in nameservers if ns in _PLAIN_DNS]
    if plain:
        return ScanResult(
            name="DNS Configuration",
            status="warn",
            message=(
                f"Plain DNS in use ({', '.join(plain)}). "
                "Queries may be visible to your ISP or resolver."
            ),
            platform="darwin",
        )
    return ScanResult(
        name="DNS Configuration",
        status="ok",
        message=f"DNS resolvers: {', '.join(nameservers[:3])}.",
        platform="darwin",
    )
run_all
run_all() -> list[ScanResult]

Run all checks, filter to the current platform, hide 'skip' results.

Source code in src/openjarvis/cli/scan_cmd.py
def run_all(self) -> list[ScanResult]:
    """Run all checks, filter to the current platform, hide 'skip' results."""
    current_plat = "darwin" if sys.platform == "darwin" else "linux"
    results: list[ScanResult] = []
    for check_fn in self._get_all_checks():
        result = check_fn()
        if result.platform not in (current_plat, "all"):
            continue
        if result.status == "skip":
            continue
        results.append(result)
    return results
run_quick
run_quick() -> list[ScanResult]

Run only critical checks: disk encryption + cloud sync agents.

Source code in src/openjarvis/cli/scan_cmd.py
def run_quick(self) -> list[ScanResult]:
    """Run only critical checks: disk encryption + cloud sync agents."""
    current_plat = "darwin" if sys.platform == "darwin" else "linux"
    quick_checks: list[Callable[[], ScanResult]]
    if current_plat == "darwin":
        quick_checks = [self.check_filevault, self.check_cloud_sync_agents]
    else:
        quick_checks = [self.check_luks, self.check_cloud_sync_agents]
    results = []
    for check_fn in quick_checks:
        result = check_fn()
        if result.status != "skip":
            results.append(result)
    return results

Functions

scan

scan(quick: bool, as_json: bool) -> None

Audit your environment for privacy and security risks.

Source code in src/openjarvis/cli/scan_cmd.py
@click.command()
@click.option("--quick", is_flag=True, default=False, help="Run only critical checks.")
@click.option("--json", "as_json", is_flag=True, default=False, help="Output as JSON.")
def scan(quick: bool, as_json: bool) -> None:
    """Audit your environment for privacy and security risks."""
    scanner = PrivacyScanner()
    results: List[ScanResult] = scanner.run_quick() if quick else scanner.run_all()

    if as_json:
        import json as json_mod

        output = [
            {
                "name": r.name,
                "status": r.status,
                "message": r.message,
                "platform": r.platform,
            }
            for r in results
        ]
        click.echo(json_mod.dumps(output, indent=2))
        return

    if not results:
        click.echo("No applicable checks for this platform.")
        return

    _render_results(results)