Skip to content

Releases: trsdn/OpenZombr

OpenZombr 0.4.2

Choose a tag to compare

@trsdn trsdn released this 22 Sep 19:29
ac0a6ac

Changed

  • All targets build in the Swift 6 language mode. Two pre-Sendable Foundation types
    that are documented thread-safe when used the way this codebase uses them — a
    FileManager instance never mutated after creation, a date formatter configured once and
    only read from — are marked nonisolated(unsafe) at their one call site each. No
    concurrency behaviour changes; the compiler now checks the claims the code already made
    in comments, such as IdleTracker's internal locking.

Documentation

  • Audit finding 5 is confirmed intentional, not a gap. A parent whose session children
    are a rotating cast of short-lived helpers (sh -c …, curl, leak plumbing) rather than
    one persistent process can never produce a readable CPU idle signal, because
    IdleTracker needs to see the same pid twice. That keeps the parent's session signal
    permanently unreadable, which AGENTS.md already requires the emergency override to
    respect. TransientSessionChildTests pins this down; no behavior changed.

OpenZombr 0.4.1

Choose a tag to compare

@trsdn trsdn released this 22 Sep 13:55
262672d

Added

  • The dropdown menu opens with the application's name and version, above everything else.

OpenZombr 0.4.0

Choose a tag to compare

@trsdn trsdn released this 21 Sep 11:16
b7a6a18

Fixed

  • Sampling no longer runs on the main actor. Reading the process table, proc_pidinfo
    per session child and the tails of session logs happens on a background thread, so a
    slow sample on a machine that is out of slots can no longer freeze the menu bar item or an
    open menu. A poll that finds another one still sampling is skipped rather than queued, and
    a manual cleanup waits for a sample in flight instead of racing it. IdleTracker is now
    internally synchronised, since it is no longer confined to the main actor.

  • The denylist no longer fails open where it is blind. Executable paths were resolved
    for the 20 largest offenders only, so a path-based deny entry could not match the rest,
    which were compared on their 16-character p_comm alone. Every offender is now resolved,
    and a parent whose path is still unknown is refused whenever a denylist is configured.

  • A target that exits between the liveness check and the identity check is reported as
    gone
    , not as identity-changed. A successful SIGTERM in that window used to be logged
    as a failure. A live process whose identity cannot be read is still never signalled.

  • Session logs untouched for longer than the reader's horizon are no longer opened on
    every poll.
    Nothing can be written after a file's modification time, so the file cannot
    hold newer activity. This bounds the per-poll cost on a directory that accumulates logs;
    mtime is still never used to make a session look recent.

  • "Jetzt aufräumen" no longer acts on a stored snapshot older than 60 s when the fresh
    sample fails.
    Identity verification covers pid reuse but not a session that became
    active since; the snapshot can be an hour old. The run is refused with a visible message.

  • Time the Mac spent asleep no longer counts as CPU-idle time. A session worked in at
    midnight read as eight hours idle at breakfast. The tracker now compares the wall clock
    with the awake clock and subtracts the difference. Both idle signals still have to agree.

  • Candidate ordering is a strict weak ordering. An unreadable log age tied with an age of
    0 without falling through to the zombie count, so the order depended on table layout.

  • Log directories are read a bounded number of times per poll. More than 32 files that
    need reading make the directory unknown — which protects the session — rather than
    reading an unbounded amount or guessing from a subset.

Changed

  • A plain allowlist pattern names a program, not a directory. agency matched any
    executable under a folder whose name contained it. Patterns without a / are now matched
    against the process name and executable file name; patterns with a / still match the
    whole path. The denylist is unchanged and still vetoes on name or path.

Tests

  • The path from poll() to the destructive path is now covered at model level: the
    confirmation gate, the cooldown, disabled auto-cleanup and the halted state.

OpenZombr v0.3.0 — Signed, notarized, and self-updating

Choose a tag to compare

@trsdn trsdn released this 16 Sep 19:27
  • Releases are now Developer ID-signed and notarized. No more clearing the quarantine attribute by hand.
  • In-app updates: a daily background check (can be switched off), "Nach Updates suchen …", and install-and-restart from the menu. An install waits for a running cleanup to finish, and automatic checks wait while the process table is critical.
  • The emergency override keeps reaping until the machine is projected to be healthy, instead of one parent per run.

v0.2.0 and earlier are ad-hoc signed and cannot update themselves: install this release manually once. See CHANGELOG.md for details.

v0.2.0 — the log signal reads content, not mtime

Choose a tag to compare

@trsdn trsdn released this 29 Aug 10:16

The cleanup could never touch the process this app was built for.

The liveness guard read the log signal as the newest write in the session's --log-dir. That file was refreshed every 30 s by the leak's own telem flush: spawned detached child pid=… heartbeat — and each of those lines is one of the zombies to be cleaned up. The leaking wrapper was protected by its own leaking.

Measured on 2026-08-29: wrapper 86183, 17 h old, 1022 zombies at 39 % of kern.maxprocperuid, CPU idle for 7.6 h, log age reported as 4 s. Never offered as a candidate. Since both signals must agree, the log signal alone neutralised the whole cleanup feature.

The age is now derived from the log content: the tail is read backwards from EOF and the scan stops at the first line that is not a known heartbeat. The 253 MB log of that session is judged in 60 ms against a 60 s poll interval.

The safety direction is unchanged. Classification is a denylist, so an unrecognised format counts as real work and protects; a file with no parseable timestamp makes the whole directory unknown. The hard guards — PID 1, foreign uid, ancestry, zombie threshold, allowlist — are untouched.

Also in this release: --log-probe for auditing a log directory by hand, GitHub Actions for build, test and release, and install instructions.

130 tests. See CHANGELOG.md for the full entry and the measurements behind each decision.


This build is ad-hoc signed, not notarised. Verify the checksum against checksum.txt, then:

xattr -dr com.apple.quarantine /Applications/OpenZombr.app

Given that this app sends SIGKILL on your behalf, building from source (make install) is the better option. Auto-cleanup is off by default — watch --idle-watch on your own machine first.