Troubleshooting

Common errors, their root causes, and how to resolve them. Most of what looks like a failure is Obexum being honest — a check it could not run safely is reported, never silently dropped.

First, the mental model. A scan resolves the target once (OS, privilege, available commands), then runs every applicable check against that snapshot. Each check ends in one of four states: PASS, FAIL, SKIP (a precondition was not met), or ERROR (the check could not complete). Obexum is read-only during a scan — it probes, it never changes the target. Almost every issue below is a SKIP or an ERROR, not a broken scan.

1. Connectivity & authentication

Obexum reaches a target one of two ways: locally (the host running the binary, local://localhost) or over SSH (ssh://user@host:port). Windows targets are reached over OpenSSH with PowerShell as the default shell — the same SSH transport as Linux/BSD, not WinRM. If a scan never gets past host resolution, the error is in the SSH layer.

SSH key cannot be read or parsed

# ssh: read key: open /home/you/.obexum/keys/obexum_ed25519: no such file or directory
# ssh: parse key: ssh: this private key is passphrase protected

Dial / connection failures

# ssh dial 10.0.0.5:22: dial tcp 10.0.0.5:22: connect: connection refused
# ssh dial 10.0.0.5:22: ... i/o timeout

Host key / known_hosts mismatch

By default Obexum uses trust-on-first-use: it learns and pins the host key into ~/.ssh/known_hosts the first time it sees a host. If you set target.strict_host_key: true and the key is not already pinned — or the host was rebuilt and its key changed — the connection fails with a knownhosts key error. Remove the stale line for that host from ~/.ssh/known_hosts (or point target.known_hosts_path at a fresh file) and re-run.

Authentication rejected

# ssh dial 10.0.0.5:22: ssh: handshake failed: ssh: unable to authenticate

The public key is not authorised on the target. On Linux/BSD copy it with ssh-copy-id -i ~/.obexum/keys/obexum_ed25519.pub user@host. On Windows, the public key must be in the target user's authorized_keys (for an admin account that is C:\ProgramData\ssh\administrators_authorized_keys), and the OpenSSH default shell should be PowerShell. Confirm you can run ssh user@host powershell.exe -NoProfile -Command "$PSVersionTable.PSVersion.Major" by hand — Obexum decides a target is Windows by exactly that probe.

2. “Permission denied” / needing root or Administrator

Many checks read state that only a privileged account can see — /etc/shadow, audit policy, the local Administrators group, firewall configuration, BitLocker/LUKS status. Obexum detects your privilege level once during host resolution:

Checks that declare a root prerequisite and do not have it are reported as SKIP — requires root, not as errors and never as findings. They simply do not run, which means a non-privileged scan undercounts your real exposure.

Fix. Scan as a privileged identity. On Linux/BSD run Obexum under sudo for a local scan, or use an SSH user that is root (or has unrestricted sudo). On Windows use an account in the local Administrators group; for Active Directory checks on a Domain Controller that account also needs the matching domain privileges. The scan summary's SKIP count and the per-check audit: line on stderr tell you how much coverage you lost.

3. Lots of checks showing SKIP

A large SKIP count is usually correct behaviour, not a bug. Obexum only runs a check when its preconditions hold on this host. A check that inspects PostgreSQL is meaningless on a box with no PostgreSQL, so it skips with a visible reason instead of inventing a finding. The skip reason is recorded for the report's coverage section — nothing is hidden.

The reasons you will see, and what each means:

Skip reasonMeaning
requires rootThe check needs root / Administrator and the scan is not privileged (see §2).
missing command XA tool the check relies on (e.g. auditctl, nft, Get-MpPreference) is not installed/available on the target.
missing file XA config file or path the check inspects does not exist — usually the feature is not installed.
requires pkg manager X (have Y)A package-state check that only applies to a different package manager (e.g. a dpkg check on an rpm host).
no listener on port PA service-specific check whose port is not being listened on, so the service is not running.
platform X not in [...]The check targets a different OS family (a Debian-only check on RHEL, a Linux check on Windows, etc.).

So “hundreds of skips” on a minimal server is expected: the catalogue spans Windows AD, multiple Linux families and BSD, and most of it does not apply to any single host. If a check you expected to run is skipping, read its reason — it will name the missing command, file, port or privilege you need to provide.

4. Reporting a suspected false positive

Obexum's contract is zero false positives on a clean baseline, and every finding ships with its evidence so you can check our work. Each finding's evidence trail is the literal command(s) Obexum ran and the literal output it got back — that is what makes a finding falsifiable. Before reporting, inspect it:

# list the rule_ids + counts in the latest engagement
obexum findings list

# show one finding's full evidence (commands + raw output)
obexum findings show AUD-WIN-ADCS-001

If you have applied a fix and the finding should now be gone, the fastest way to confirm (and to catch a stuck detection) is the verify-loop. The portal's “Verify” panel on each finding issues a short-lived token; Obexum re-runs just that one rule and reports back whether it still fires:

# re-run a single rule against the host after remediation
obexum verify --upload-token=$VERIFY_JWT

If a check fires on a configuration you are certain is correct, that is a finding we want. Email security@obexum.com (probe behaviour / a check that fires when it should not) or docs@obexum.com (everything else), and attach:

Why the evidence matters. A bidirectional check fires when the misconfiguration is present and stops the instant it is removed. If the command + output in the evidence trail show the box is actually clean, that is a reproducible bug report — not your opinion versus ours — and it is the fastest path to a fix.

5. Reading errors in scan output

A handful of message shapes show up on stderr during a scan. Knowing which are fatal and which are advisory saves a lot of worry:

MessageSeverity
scanner error: ...Advisory. One bundled engine (e.g. an external scanner) failed; the scan continues with the rest.
audit engine warning: ... / ... warning: ...Advisory. An optional stage (enrichment, rules, tracker) had a problem and was skipped; findings are unaffected.
A check ending in ERRORInconclusive, never a fail. The check could not complete (parse error, transport hiccup, a per-check 60s timeout). Re-run; if it persists, send us the rule_id.
check panicked: ...A single check hit an internal error. It is isolated — the run does not crash — and surfaces as that check's ERROR. Worth reporting.
upload: ...Advisory. The scan succeeded and the local report was written; only the portal upload failed. Your audit artifact is safe on disk.

The end-of-run summary is the authoritative tally — severity counts plus the patch-posture block. An ERROR is treated as “inconclusive”, so it is counted separately and never inflates your FAIL numbers.

For more detail on what a check did or why it skipped, raise the log level:

obexum scan --target ssh://Administrator@10.0.0.5 --log-level debug

6. Where logs and the engagement directory live

Logs are written to stderr, not to a separate log file. They are human-readable by default; set logging.format: json (or scope it with --log-level trace|debug|info|warn|error) for structured output you can pipe into a collector. To keep a copy of a run, redirect stderr yourself:

obexum scan --target ssh://Administrator@10.0.0.5 2> scan-$(date +%F).log

Reports and findings are written under the engagement directory. With the defaults from obexum init, each scan gets its own folder:

~/.obexum/scans/<scan_id>/
  ├── findings.json      # every finding + full evidence trail
  ├── findings.html      # the report you open in a browser
  └── manifest.json      # index of the run (target, counts, files)

The obexum findings list / show commands read this directory and default to the most recent engagement; pass --scan <scan_id> to inspect an older one. The location is controlled by output.engagement_dir (and the flat report copy by output.path) in your config — if you cannot find your reports, check those two keys. JSON is always written to the engagement directory even if it is not in output.formats, so the evidence trail is never missing.

Still stuck?