Skip to content

Repository files navigation

Umai

A supply-chain scanner for the Mac you actually work on.

Platform Swift License

Umai inventories every place software can hide on a Mac, then checks all of it against the same advisory data the rest of the industry uses. Global npm and pip packages, your projects' lockfiles, Python environments, Go binaries, editor and browser extensions, Mac apps, launch agents, and the MCP servers your AI tools start behind your back. One scan, about four seconds, and the matching happens on your machine.

Umai on first launch

Why I built it

Every supply-chain attack of the last few years landed the same way: a package you never chose showed up on your machine because something you did choose pulled it in. event-stream reached 8 million downloads with a malicious transitive dependency inside. jeIlyfish sat on PyPI stealing SSH keys, one capital I away from a package people install every day.

Now the install button presses itself. An AI agent runs npm install, an MCP config runs npx -y some-server at every launch, and nothing on the machine keeps a record of what arrived. Your lockfiles know about your project. Nothing knows about your Mac.

That is the gap Umai fills. It is not a linter for your repo and not an antivirus. It is the answer to "what actually landed here, and can I trust it".

What it looks at

Sixteen surfaces in one pass. The ones with an advisory feed get real vulnerability matching. The ones without get inventoried, signature-checked and tracked for drift, and Umai tells you that is all it did.

Surface What Umai does
Project lockfiles found in your home folder Full transitive tree from package-lock.json (v1 to v3), yarn.lock (classic and berry), pnpm-lock.yaml, bun.lock, Gemfile.lock, Cargo.lock, go.mod, poetry.lock, uv.lock, pinned requirements.txt. Matched against OSV and KEV
npm, yarn, pnpm, bun globals Read from each manager's own lockfile, no CLI needed. Matched
pip, virtualenvs, pipx, uv tools Environments discovered on disk through pyvenv.cfg and dist-info names. No interpreter is ever executed. Matched
RubyGems, Cargo Installed gems and cargo install binaries. Matched
Go binaries Exact module versions read out of the compiled binaries in ~/go/bin with go version -m. Matched
MCP servers Configs of Claude Desktop, Claude Code, Cursor, Windsurf and VS Code are parsed (never executed). A pinned npx/uvx package gets full matching; an unpinned one gets checked against known-malicious names and flagged, because it fetches whatever is newest every time your client starts
Mac apps /Applications and ~/Applications, with the code-signature provenance macOS can verify: App Store, Developer ID with the team name, ad-hoc, or unsigned
Launch agents and daemons The plists in ~/Library/LaunchAgents, /Library/LaunchAgents and /Library/LaunchDaemons, what each one runs, and whether that program is signed. Items that run through /bin/sh -c are marked as such, because the shell's Apple signature says nothing about the script it executes
Editor extensions VS Code, Cursor, VSCodium, Windsurf. Inventory and drift
Browser extensions Chrome, Brave, Edge, Arc, Chromium, Firefox. Inventory and drift
Homebrew Formulae and casks, plus brew outdated staleness. Inventory and drift, since no public feed maps Homebrew reliably

What you get back

Not a wall of CVEs. Umai opens with a verdict, then a plan.

The dashboard after a scan

Installed is not the same as mentioned. This is the idea the whole app is built around. A vulnerable package sitting in some lockfile in ~/Downloads is not the same problem as one installed globally on your machine, and treating them alike is how scanners train people to ignore them. Umai splits them: findings that are actually on the Mac go into your action plan, findings that only exist in a project's manifest are listed separately and clearly labelled with the file they came from.

Findings, grouped by surface, with the action plan first

Findings are triaged, not counted. Everything lands in one of these:

  • Needs your action: installed here, fixable or worth removing
  • In project files: declared somewhere, not installed, becomes real when you install that project
  • Comes with macOS: Apple's own Ruby gems and friends, which Apple patches and you should not touch
  • Old projects: findings in folders you have not opened in 90 days, parked out of the way
  • Suspicious: typosquats, npm install scripts, unsigned apps and launch items, always ranked below confirmed findings because a hunch is not a CVE

Known-malicious packages and actively exploited vulnerabilities (CISA KEV) break every rule above. They sort first, they cannot be parked, and they cannot be ignored.

A finding expanded, with the plain-English risk and the fix

Fixes that are honest about what they do. Expand a finding and you get one plain sentence about what the risk actually means, then a command. Press Fix and Umai runs exactly the command it just showed you, as a direct tool invocation, never through a shell and never with sudo. Then it rescans to check. If the fix reported success but the finding survived, which happens when a semver range blocks the real fix, Umai says so and names the manifest you need to edit. The command always comes from the tool that owns that lockfile, so a uv.lock finding gets uv lock --upgrade-package and not a pip command that would quietly change the wrong environment.

Drift. Every scan is a snapshot. The Changes page shows what appeared, changed or disappeared since last time, grouped by project, so the twelve packages that showed up in one folder overnight are impossible to miss.

Changes since the previous scan

Reports. One click gives you a PDF with the verdict, the numbered action plan, the finding tables and a footnote listing what the scan could not check. Or a CycloneDX 1.5 SBOM with package URLs and the confirmed findings attached, for the tooling that expects one. Both are generated offline.

The exported PDF report

Install

Download the latest DMG from Releases, drag Umai to Applications, done. Builds are signed with a Developer ID and notarized by Apple, so Gatekeeper opens them without the right-click ritual.

The installer

Building it yourself is just as easy, since there is nothing to install first:

git clone https://github.com/sametsazak/umai.git
cd umai
open umay.xcodeproj    # then press Run

Requires macOS 14 or later. Xcode 26 to build. The Liquid Glass styling switches on when you are on macOS 26.

Using it

The first run downloads the advisory databases, about 250 MB, and that is the only time it happens. macOS will ask for permission to read Desktop, Documents and Downloads while the first scan walks your project folders. Saying no is fine; Umai skips what it cannot read and counts it.

After that it is one button. A scan takes a few seconds on a normal machine and touches nothing: every collector is read-only (npm ls, pip list, brew list, reading files on disk). The app only changes something when you press Fix, and it shows you the command first.

Keyboard and Shortcuts. "Scan This Mac" is an App Intent, so it runs from Shortcuts, Spotlight and Siri, and reports the verdict back. Handy for a login trigger or a weekly automation.

If you want it to watch. Background protection is off by default and stays off until you turn it on. Enabled, it puts a shield in the menu bar, runs one quiet scan a day, and notifies you only when something new needs your action. When the advisory database refreshes with new data, Umai re-scores your last inventory immediately, so a fresh critical advisory reaches you without waiting for tomorrow's scan.

The menu bar shield

Ignore, with memory. Hide a finding you have decided about and it stays hidden for that exact version in that exact location. Change the version and it comes back. Malicious and actively exploited findings cannot be ignored at all.

How this differs from a lockfile scanner

If you want to scan a repository in CI, use osv-scanner or trivy. They are good at that, they run everywhere, and Umai does not try to replace them.

Umai answers a different question. Those tools scan the code you point them at. Umai scans the machine: the globally installed packages, the apps, the login items, the browser and editor extensions, the MCP servers, and yes, the lockfiles too, but with the machine as the subject rather than the repo. It knows the difference between something installed and something merely mentioned, and it remembers what your machine looked like yesterday.

Different questions, and you probably want both.

How it works

The advisory data comes from OSV.dev as per-ecosystem bulk feeds (npm, PyPI, crates.io, RubyGems, Go), which already aggregate GitHub Advisories, RustSec, PyPA and the OpenSSF malicious-packages feed, plus the CISA KEV catalog for the actively-exploited badge. Everything lands in a local SQLite store and refreshes with ETag-conditional requests, so a refresh that changes nothing costs one HTTP 304.

Matching is done here, not in a service. Version ranges are evaluated per ecosystem with comparators written for the job: SemVer for npm and crates, PEP 440 for Python (with its epochs, post and dev releases), Gem::Version rules for Ruby. CVE aliases are deduplicated so one vulnerability counts once, withdrawn advisories are dropped, and severity comes from the advisory label or gets computed from the CVSS v3 vector when the label is missing.

Two rules shaped a lot of the code. Umai never runs your shell startup files, so a trojaned .zshrc cannot influence a scan; the PATH is assembled from /etc/paths, /etc/paths.d and the known version-manager directories. And Umai has no third-party dependencies at all: system SQLite, system frameworks, nothing else. A tool that reads your whole disk should not arrive with a supply chain of its own.

Every parser reads untrusted input, so they are tested against hostile input on purpose: seeded random garbage, four-thousand-level nested JSON, two-megabyte single lines, package names shaped like shell injections. 160+ tests run in about a second.

What it deliberately does not do

  • No telemetry, ever. There is no analytics endpoint to disable, because there is none.
  • No real-time monitoring. No kernel extension, no file-system watcher, no daemon. Umai runs when you press Scan, or once a day if you asked it to.
  • No curated IOC list. extensions-denylist.txt ships empty on purpose. Curating indicators is a full-time job and doing it badly is worse than not doing it; malicious-package detection comes from the live OSV and OpenSSF data instead.
  • No vulnerability claims where there is no data. Homebrew, Mac apps, browser and editor extensions have no public advisory feed that maps them reliably. Umai inventories them, checks signatures where it can, tracks drift, and says exactly that in the results instead of showing you a green tick that means nothing.

Privacy

Your package list never leaves the machine. There is no account, no server, nothing to sign in to.

Umai makes exactly two kinds of network request:

  1. Downloading the advisory databases from OSV.dev and CISA, which are the same generic files everyone else downloads. No information about your machine is sent with them.
  2. An optional once-a-day check of GitHub's release metadata so you hear about updates and security fixes. It sends nothing about your Mac, downloads nothing on its own, and has an off switch in Settings.

Scan history lives in ~/Library/Application Support/Umai and is used for the drift comparison. Diagnostic logs go to ~/Library/Logs/Umai, stay local, and are reachable from the Help menu if you ever need to attach one to an issue.

FAQ

Is this an antivirus? No. An antivirus watches your machine continuously and looks for malware behaviour. Umai takes an inventory when you ask it to and compares it against public vulnerability data. They solve different problems and you can happily run both.

Does it slow my Mac down? A scan reads files and runs a few read-only commands for a few seconds. Nothing stays resident unless you turn on background protection, and even then it is one scan a day.

Why does it want access to Documents and Downloads? Because that is where projects live, and project lockfiles are where transitive dependencies hide. If you decline, Umai scans what it can and reports the folders it skipped.

A finding looks wrong. What do I do? Open an issue with the package name, version and advisory ID. A false positive costs this tool more than a miss does, so those get fixed first.

Can I use it in CI? Not yet. It is a Mac app today; a command-line version is on the roadmap.

Contributing

Issues and pull requests are welcome, especially new surfaces. CONTRIBUTING.md has the ground rules, and the short version is: no third-party dependencies, nothing leaves the machine, every feature ships with tests.

xcodebuild -project umay.xcodeproj -scheme umay test

Security issues go to SECURITY.md instead, privately.

License

MIT. See LICENSE and ATTRIBUTIONS.md for the data sources.

About

Native macOS app that inventories what is actually installed on your Mac (16 surfaces) and checks it against OSV.dev and CISA KEV, fully on-device.

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages