Engagement Report: Two Stacked Bugs Behind One Error Message

A fifth redacted-format report: Lineman's own integrity-verification script crashed on a missing argument. Underneath that crash was a second, unrelated bug that would have kept the test failing even after the first one was fixed.

Fifth sample engagement report, on Lineman, a macOS host intrusion-prevention daemon with egress forensics and process-lineage tracking. The target is its own verification tooling: two scripts, verify_integrity.py and verify_v2.py, built to confirm the daemon's core logic actually does what it claims.

Scope and Objective

Target: verify_integrity.py (manifest integrity, egress-classification logic, logic-drift scanning) and verify_v2.py (Ed25519 payload signing, tamper detection, BSM audit-pipe availability).

Objective: run both scripts fresh, tonight, and treat a passing result as something to verify rather than something to assume.

Finding: A Crash Hiding a Second, Independent Bug

verify_integrity.py failed immediately: classify_destination() missing 1 required positional argument: 'dst_port'. The function's real signature takes four arguments (sni, dns_hint, dst_ip, dst_port); the test's call site supplied three, with the test's port value landing in the dst_ip slot and no dst_port at all. A straightforward argument-count mismatch, the kind any type checker would catch.

Fixing only that would have been the wrong lesson. The same line also did this: cls, soc = egress_forensics.classify_destination(sni, "", port). The function returns a dict, {"classification": ..., "soc_alert_id": ...}, not a two-item tuple. Unpacking a dict with a, b = some_dict doesn't raise an error in Python; it silently iterates the dict's keys, so cls and soc would have become the literal strings "classification" and "soc_alert_id", never the actual values, on every single test case. Confirmed directly:

>>> cls, soc = egress_forensics.classify_destination('metrics.apple.com', '', '0.0.0.0', 443)
>>> cls, soc
('classification', 'soc_alert_id')

The first bug was loud: a TypeError that stops execution and gets noticed immediately. The second was silent: a test that would have run to completion and failed every assertion with a confusing mismatch, or worse, depending on how the comparison was structured, could have passed for the wrong reason. The first bug's crash was, in a strange way, protective: it meant nobody had actually seen the second bug's failure mode yet. Both are fixed now: the call site supplies all four real arguments, and the result is read by its real dict keys.

Verification: Full Re-Run, Both Scripts, Tonight

verify_integrity.py, after the fix:

✓ Manifest is valid and contains all Ghost-Protocol IDs.
✓ Forensics classification logic verified (SOC Alert mapping intact).
✓ No critical logic drift detected in restored components.
[SUCCESS] All integrity checks passed. Ghost-Protocol logic restored.

verify_v2.py, unmodified, needed no fix:

✓ Signature generated
✓ Signature verified successfully.
✓ Tamper detection verified (Invalid signature caught).
⚠ WARNING: /dev/auditpipe requires root. Skipping live BSM test.
[SUCCESS] Lineman v2.0 elevation verified.

The Ed25519 signing and tamper-detection path is real and correct: a payload gets signed, the signature verifies against the correct payload, and a deliberately invalid signature is correctly rejected rather than silently accepted. The BSM (audit pipe) check does exactly what a verification script should do when it can't complete a test: it names the reason, root access to /dev/auditpipe, and skips rather than faking a result. That honesty is worth noting on its own; a verify script that fakes a pass under a permission constraint is worse than one that admits the gap.

Mitigations and Next Steps

  1. A caught TypeError is a lucky accident, not a safety net. The dict/tuple unpacking bug would have produced no exception at all; it would have just quietly compared the wrong values. Any function whose return shape changes (dict, tuple, dataclass) needs its call sites checked explicitly, not assumed compatible because nothing crashed.
  2. Prefer a typed return (a small dataclass, or a TypedDict with a type checker in CI) over a bare dict for any function whose result gets destructured elsewhere. This exact bug class, destructuring the wrong shape, is invisible at review time and only surfaces when someone actually runs the code against real values.
  3. Keep the honest-skip pattern. The BSM test's explicit root-gated skip is the right model for every other environment-constrained check in this codebase. Don't replace it with a mock that always passes.

GhostInThePrompt.com // The crash was the easy bug. The one underneath it would have passed silently.