Skip to content
subcloakdocs

Reference

watchctl

Run Cloak Watch's engine from Terminal with watchctl: scan a Mac, list its inventory and footprint, check domains and package names, and verify the signed feed.

watchctl is Cloak Watch’s engine on the command line, for scripts, CI and anyone who prefers Terminal. It runs the same inventory, matching and checks as the app. It reads: the one thing it writes is its own store, and only with --store. Three commands do more, and say so: quarantine restore moves back a file Watch quarantined, guard remove takes away install blocking’s scripts and profile line, and domain makes DNS lookups and a TLS connection from this Mac. watchctl never stops a program, revokes a token or sends a key anywhere.

watchctl ships inside Cloak Watch, at Cloak Watch.app/Contents/Resources/watchctl, so it updates with the app. To run it as watchctl from Terminal:

  1. Open Settings › Advanced and click Install Command-Line Tool….
  2. Copy the command it shows and run it once in Terminal. It asks for your password; Watch never changes that folder itself.
Terminal
sudo mkdir -p /usr/local/bin && sudo ln -sf "/Applications/Cloak Watch.app/Contents/Resources/watchctl" /usr/local/bin/watchctl

This links /usr/local/bin/watchctl to the copy inside the app. Settings › Advanced then says Installed in /usr/local/bin. To remove it, run sudo rm /usr/local/bin/watchctl.

Command What it does
watchctl scan Inventory, feed matching, checks and secrets; prints the incidents
watchctl inventory What’s on the Mac: counts by kind, or every item as JSON
watchctl match Affected or not, for each incident pack you give it
watchctl watch Scans, then rescans on file changes and prints new incidents
watchctl quarantine Lists Quarantine, or puts one item back
watchctl checks Reads trust stores, versions, services and workflows, then answers each headline’s checks
watchctl footprint New in 1.2.0 Lists the repositories, domains, servers, clusters, cloud accounts and registries this Mac’s config files name
watchctl domain New in 1.2.0 Runs the domain checks for the domains you name
watchctl playbook New in 1.2.0 Prints the “I think I’ve been hit” plan for this Mac
watchctl processes New in 1.2.0 Lists running programs that match a Watch mode rule
watchctl xprotect New in 1.2.0 Prints Apple XProtect’s versions and update setting
watchctl check-name New in 1.2.0 Checks a package name before you install it
watchctl guard New in 1.2.0 Shows or removes install blocking’s scripts and shell-profile line
watchctl service-catalog Prints the built-in service catalogue
watchctl feed Verifies the signed feed and checks pack files
watchctl help Prints the usage (also --help, -h, or no command)
watchctl --version Prints the version

watchctl render-icon --out DIR writes the app icon files; it’s used by the app’s build and isn’t needed otherwise.

These options work with scan, inventory, match and watch. checks takes --roots, --home and --system-root.

Option Meaning
--roots DIR,DIR Code folders to read, comma-separated. Default: whichever of ~/Developer, ~/code, ~/src, ~/Projects, ~/dev, ~/repos, ~/git, ~/workspace, ~/Code and ~/GitHub exist.
--home DIR Treat this folder as the home folder (for testing against a copy)
--system-root DIR Treat this folder as / for system paths such as /Library and /Applications
--no-caches Skip download caches
--no-secrets Skip the plain-text secrets checks (SEC-1 and SEC-3)
--history Also check shell history (SEC-2), which the app doesn’t
Terminal window
watchctl scan [--json] [--roots DIR,DIR] [--pack FILE]... [--packs DIR] [--index FILE] [--store PATH]
[--no-secrets] [--history] [--no-caches] [--home DIR] [--system-root DIR] [--all]

Runs a full scan and prints a summary, any gaps, and the incidents by tier with their evidence and steps.

Option Meaning
--pack FILE An incident pack to match. Repeat for more.
--packs DIR Every pack in a folder. Other JSON files are skipped with a note.
--index FILE A malicious-version index file, as saved by feed client
--store PATH Keep state in a store at this path, so the starting point and dismissals work across runs
--json Print the whole report as JSON
--all Print every Good to know incident and all evidence and steps, instead of the first few

watchctl doesn’t download the feed. Without --pack, --packs or --index it runs only the built-in checks.

Exit status: 2 when anything needs fixing now (Act now), 0 otherwise, 1 on an error. Use it in CI:

Terminal
watchctl scan --roots ~/code/shopapp --packs ./packs || echo "Act now: check the output"

Example output, against a test Mac with one malicious package:

2 packages, 0 extensions, 0 MCP servers, 0 skills; 1 known-malicious; 0 secrets in plain-text files
Scanned 1 projects, 5 items, 4 files in 0.0 s (inventory 0.0 s, matching 0.01 s).
○ Safari extensions: Safari keeps its extension list where only apps with Full Disk Access can read it. (Grant Full Disk Access in System Settings › Privacy & Security)
Act now (1)
▲ [PKG-1] Hollowtide npm worm (quickcache-utils-demo / quickcache-store-demo)
Affected: yes, because quickcache-utils-demo@6.0.0 installed, but it arrived outside the live window (and 1 more).
· ~/code/shopapp/node_modules/quickcache-utils-demo/package.json
· ~/code/shopapp/package-lock.json
1. Stop the launch item com.user.gh-token-monitor and quarantine its plist.
2. Quarantine ~/.local/bin/gh-token-monitor.sh.
3. Quarantine ~/.config/gh-token-monitor/.
4. Remove the SessionStart hook running “setup.mjs” from .claude/settings.json.
FYI (2)
○ [AGT-5] Claude Code may run any shell command without asking
Affected: yes, because ~/code/shopapp/.claude/settings.json allows Bash(*).
· ~/code/shopapp/.claude/settings.json
○ [PKG-8] No install cooldown is set
Affected: yes, because A 7-day cooldown would have kept most 2025–2026 hijacked versions off this Mac.
· ~/.npmrc
1. Set a 7-day cooldown in Install Guard (npm min-release-age, pnpm minimumReleaseAge, uv exclude-newer).
Packs: 1 affected, 0 not affected.

▲ marks Act now, ● Review and ○ FYI; lines starting ○ under the summary are gaps. Incidents from the starting point are marked (baseline). watchctl prints steps; it never runs them.

Terminal window
watchctl inventory [--json] [--kind KIND] [--roots DIR,DIR] [--home DIR]

Lists what’s on the Mac, by tab and kind, with packages broken down by ecosystem and where they were found:

5 items (2 distinct packages) in 1 projects, 0.0 s.
Packages: 4
Package: 4 (npm 4; installed 2, lockfile 2)
AI agents & MCP: 1
Agent settings: 1
○ Safari extensions: Safari keeps its extension list where only apps with Full Disk Access can read it.

--json prints every item. --kind limits it to one kind: package, pythonStartup, homebrewTap, editorExtension, browserExtension, mcpServer, agentSkill, agentConfig, agentHook, rulesFile, editorTask, launchItem, shellProfile, gitHook, gitConfig, workflow, registryConfig, credentialFile, dockerImage or hostArtifact.

Every MCP server on this Mac, as JSON
watchctl inventory --kind mcpServer --json
Terminal window
watchctl match --pack FILE [--pack FILE]... [--json] [--roots DIR,DIR]

Answers “am I affected?” for each pack: the verdict, why, the evidence and the steps. It warns if a pack fails validation. Exit status: 2 when any pack affects this Mac.

▲ Affected: Hollowtide npm worm (quickcache-utils-demo / quickcache-store-demo) (CW-TEST-0001)
Affected: yes, because quickcache-utils-demo@6.0.0 installed, but it arrived outside the live window (and 1 more).
· quickcache-utils-demo@6.0.0 installed
· quickcache-utils-demo@6.0.0 pinned in package-lock.json
1. Stop the launch item com.user.gh-token-monitor and quarantine its plist.
…

A pack that doesn’t affect the Mac prints ✓ Not affected: and the reason.

Terminal window
watchctl watch [--seconds N] [--roots DIR,DIR] [--pack FILE]...

Scans once, then watches your code folders and the places attacks persist for file changes, rescans only what changed, and prints new incidents as they appear. It runs until you press Control C, or for N seconds.

Watching 14 folders. 1,240 packages, 38 extensions, 7 MCP servers, 4 skills; 0 known-malicious; 0 secrets in plain-text files
[2026-10-23T09:12:40Z] 312 changed paths → rescan 0.42 s, 1 new cards
Review: …

The app does the same: with Watch for changes as they happen on in Settings › General, it rechecks what changed within seconds, and runs a full scan once a day.

Terminal window
watchctl quarantine list [--root DIR]
watchctl quarantine restore ID [--root DIR]

list prints each record’s ID, kind (moved or backup), whether it was restored, the original path and the reason. restore ID puts that item back, as Put Back does in the app. --root uses a different Quarantine folder than ~/Library/Application Support/Cloak Watch/Quarantine.

20261023-091512-4f2a9c1e moved ~/Library/LaunchAgents/com.user.gh-token-monitor.plist Launch item com.user.gh-token-monitor
Terminal window
watchctl checks --headlines FILE [--only H-ID,H-ID] [--json] [--roots DIR,DIR]

Answers Check my Mac for each headline in FILE, a file of headlines in the feed’s format, without the full package scan. It reads this Mac’s trust stores, apps and tools, services and workflows (and Homebrew and your projects’ files), then prints one answer per headline: AFFECTED, RELEVANT TO YOU, NOT AFFECTED or NOT CHECKED, each check’s answer, the places behind it, what was checked, and numbered steps.

Option Meaning
--headlines FILE The headlines to answer. Required.
--only H-ID,H-ID Answer only these headlines
--json Print the answers as JSON
--all Also list places that were checked and are fine (marked without !)
--roots DIR,DIR Code folders to read for workflows, as for scan
Read 14 trust stores, 23 apps and tools, 9 services, 31 workflows in 1.2 s (412 files).
H-2026-0001 RELEVANT TO YOU Example breach at a package registry
This Mac uses npm (1 file). Watch can't see your npm account.
- [serviceFootprint] Relevant to you: This Mac uses npm (1 file). Watch can't see your npm account.
! npm login: .npmrc: Names registry.npmjs.org, changed 2 Oct 2026 ~/.npmrc
checked: Looked for npm's login and config in ~/.npmrc. Watch notes only that they exist and which hosts, profiles or providers they name, never what's in them.
1. Revoke the access tokens this Mac holds for npm, then create new ones <https://www.npmjs.com/settings/~/tokens>
2. Put the new access tokens in ~/.npmrc, and in your CI secrets

It only reads. Paths are printed with ~ for your home folder, and no secret values appear: service checks note presence and names only.

New in 1.2.0
Terminal window
watchctl footprint [--counts] [--roots DIR,DIR]

Prints the Footprint: one line per repository, domain, server, cluster, cloud account or registry this Mac’s config files name, with its type, provider, where it was found and the file. --counts prints how many of each type and provider instead. It reads files only and contacts nothing.

New in 1.2.0
Terminal window
watchctl domain NAME [NAME…]

Runs the domain checks for each domain you name: its CNAME chain (with “(no address: dangling)” for a CNAME that points at nothing), any DNS or TLS error, the certificate chain with each certificate’s issuer and expiry, whether macOS trusts it, and its CAA records. Then one line per finding, such as [review] DOM-4 …. When a local TLS-inspecting proxy answered, it says the real certificate wasn’t seen.

This command makes network lookups from this Mac: DNS queries through your usual resolver, and a TLS connection to each domain on port 443. Nothing goes to Subcloak.

New in 1.2.0
Terminal window
watchctl playbook [--roots DIR,DIR]

Scans the Mac and prints the I think I’ve been hit plan: its warnings (marked !), then each step (Contain, Revoke, Rotate, Verify) with its rows and up to three places for each. It only prints: it never stops, revokes or checks anything. It doesn’t download the feed, so it lists what the built-in checks find, not feed matches.

New in 1.2.0
Terminal window
watchctl processes [--bench N]

Runs Watch mode’s running-programs check once and prints one line per match: PROC-1 act now or PROC-2 review, the process ID, the program’s path, the incident or place it matched and its signature. Otherwise it prints “No running program matches a Watch mode rule.”, then how many processes it looked at and the CPU each check took. --bench N runs the check N times and also prints the cost of listing processes. Exit status: 2 when any PROC-1 program is running.

New in 1.2.0
Terminal window
watchctl xprotect [--last-run]

Prints XProtect’s rules version and when they were installed, XProtect Remediator’s version, and whether Install Security Responses and system files is on. --last-run also reads the system log for when XProtect Remediator last ran, in the last 3 days.

New in 1.2.0
Terminal window
watchctl check-name ECOSYSTEM NAME [VERSION] [--index FILE]

Checks a package name as Install Guard does: whether it looks like a popular package, and whether it has letters from outside the Latin alphabet or invisible characters. With --index FILE (a malicious-version index saved by feed client), it also says whether the feed lists it as malicious. ECOSYSTEM is npm, pypi, cargo, gem, go or composer (or a name such as crates.io, rubygems or packagist). Nothing is sent. Exit status: 2 when the version is on the malicious list.

New in 1.2.0
Terminal window
watchctl guard status | remove [--home DIR]

Install blocking is coming soon and isn’t set up by Cloak Watch 1.2. If an earlier preview build set it up on this Mac, these commands show and remove what it left:

Command What it does
guard status Whether Watch’s scripts are in ~/.cloak-watch/bin, whether its handshake token is there, and for each of .zshrc, .zprofile, .bash_profile and .bashrc, whether it has Watch’s PATH line
guard remove Removes the scripts, the token and the PATH line from each shell profile, as Turn Off in Install Guard does. A profile it can’t edit is listed with the line to remove by hand (exit status 1). Open a new terminal window afterwards.
Terminal window
watchctl service-catalog

Prints the built-in service catalogue as JSON, in the same format as the feed’s service catalogue: each service’s ID, name, what kind of login it is, the files Watch looks for and which names it may read from them, and its revoke and activity-log links. The feed’s copy, when there is one, replaces entries with the same ID.

Terminal window
watchctl feed verify --root ROOT.json --repo DIR|URL [--state FILE] [--now ISO8601] [--packs] [--content-key EPOCH=HEX]
watchctl feed pack FILE.json…
watchctl feed client --url URL --root ROOT.json --cache DIR [--save-index FILE] [--lookup ECOSYSTEM/NAME@VERSION]…
Command What it does
feed verify Verifies a signed feed (a folder or URL) from a trusted root file, as the app does, and prints the sequence number and every file it lists. --packs also downloads, verifies and validates every pack (--content-key decrypts paid ones). --state keeps a trust state file so rollback is checked across runs. --now checks as of another time.
feed pack Decodes and validates pack files with the app’s rules: ok or FAIL with the problems.
feed client Runs the app’s feed client against a feed into a cache folder, with timings. --save-index writes the merged malicious-version index for scan --index; --lookup npm/NAME@VERSION looks a version up.
verified: seq 1, root v1, timestamp v1 signed 2026-10-22T12:00:00Z, expires 2026-10-22T13:00:00Z
CW-TEST-0002 r1 malicious/confirmed free broadcast packs/07b0…a3.json
CW-TEST-0001 r2 malicious/confirmed free broadcast packs/e173…36.json
index seq 1, 15 entries index/1-1a0b…28.bin

If verification fails, the command says why, for example feed verification failed: timestamp expired at ….