# Respond to an incident

> Use Cloak Watch's I think I've been hit plan to contain an attack, revoke and rotate keys in the right order, and check the old keys and the Mac are clean.

Source: https://docs.subcloak.com/cloak-watch/respond/

Added in Cloak Watch 1.2.0.

**I think I've been hit** is Cloak Watch's plan for the hours after a bad install: what to stop, which keys to revoke and rotate and in what order, and how to check the Mac and the old keys afterwards. Watch builds it from what it found on this Mac, in the order the write-ups for the 2025 and 2026 incidents called for. Nothing changes until you press a button on a row.

*[Screenshot: The I think I've been hit sheet: warnings to do Revoke and Rotate from another device and not to run the package again, then 1 Contain with Stop .helper (process 4821) and Fix 6 problems Watch found, and 2 Revoke with Revoke the 2 GitHub tokens found on this Mac and Revoke GitHub CLI's access. The plan on a test Mac with a running stealer, a malicious package and two GitHub tokens.]*

## Open the plan

Open it from either place:

- **Inbox**: click **I Think I've Been Hit…** at the top.
- The **Watch** menu: choose **I Think I've Been Hit…**.

The sheet lists four steps, each with rows for this Mac. The footer counts what's done, for example **3 of 22 done**. Click **Done** to close it. Each time you open it, Watch builds it again from what it has found.

> **Don't test it:**
> Don't run a suspect package or script again to see what it does. Some worms wipe the home folder when they can't send what they stole. The plan says so at the top whenever a known malicious package or file was found.

## The four steps

| Step | What it's for |
|---|---|
| **1 Contain** | Stop what's running and remove what persists |
| **2 Revoke** | Kill the tokens a worm uses to spread |
| **3 Rotate** | Replace every key this Mac holds, most dangerous first |
| **4 Verify** | Check the old keys are dead and the Mac is clean |

Warnings at the top of the sheet say when to change your plan:

| Warning | When it shows |
|---|---|
| "Something on this Mac may be watching what you type. Do the Revoke and Rotate steps from another device you trust, and come back here for Verify." | A program is running from a stealer's path, a known attack launch item or file was found, or another Act now card matched known malware |
| "Don't run the package or script again to see what it does. Some worms wipe the home folder when they can't send what they stole." | A known malicious package or file was found |
| "Something ran as administrator. Watch can't prove that's gone: after rotating, back up your files and reinstall macOS." | A malicious launch daemon was found in `/Library/LaunchDaemons`, or a file stealers use to keep your password |

### 1 Contain

1. **Stop programs.** A row such as **Stop .helper (process 4821)** appears for each program running from a place stealers use (see [Watch mode](https://docs.subcloak.com/cloak-watch/watch-mode/)). Click **Stop…**, then **Stop**. Watch sends the program the normal quit signal; its file stays where it is until you fix the problem.

   Just before it signals, Watch checks the process is still the one it found: the same program, started at the same time, and for a script run by `python`, `node` or another interpreter, the same script. If anything changed, it says "Nothing was stopped." It won't stop a program that runs as another user; stop that one from Activity Monitor with an administrator's password.

2. **Fix what persists.** **Fix N problems Watch found** runs [Fix all](https://docs.subcloak.com/cloak-watch/inbox/#fix-it-and-fix-all) for every problem on the Mac. Watch unloads launch items and moves files to [Quarantine](https://docs.subcloak.com/cloak-watch/quarantine/) first, in a safe order. Nothing is deleted, and everything can be put back from **Settings › Quarantine**.

When nothing is running from those places, the step says **Nothing to stop on this Mac right now**.

### 2 Revoke

Worms use stolen GitHub and package-registry tokens to spread, so these come before anything else.

| Row | What it does |
|---|---|
| **Revoke the N GitHub tokens found on this Mac** | **Revoke…** sends the tokens you tick to GitHub's revoke service, which switches them off at once, even ones you can't sign in to revoke. See [Revoke GitHub tokens](#revoke-github-tokens). |
| **Revoke the GitHub token issued by …** | A token a credential store files under another host, such as GitHub Enterprise Server. Watch never sends it anywhere: revoke it on that host. |
| **Revoke GitHub CLI's access** | The GitHub CLI keeps its token in the Keychain, which Watch doesn't read. **Open** goes to GitHub, where you revoke the GitHub CLI app, then sign in again with `gh auth login`. |
| **Revoke your npm tokens**, **Revoke your PyPI API tokens**, **Revoke your crates.io tokens**, **Revoke your RubyGems API keys** | **Open** goes to each registry's token page. |
| **Check GitHub for things you didn't make** | Repositories, runners and workflows a worm may have created with your account. |

### 3 Rotate

One row per credential Watch knows about, **Rotate …**, grouped from most to least dangerous:

1. **Code and package publishing**: GitHub and registry tokens that can publish code.
2. **Cloud, servers and SSH**: AWS, Google Cloud, Kubernetes and SSH keys.
3. **AI providers**: Anthropic, OpenAI and other AI keys.
4. **Other services**.
5. **Sessions and the Mac**: **Sign out of every other session**; **Move your crypto to a new wallet**, when a wallet app or the MetaMask extension is installed; **Change your Mac's login password**, when something may be watching what you type.

Where Watch knows the page for making a new key, the row has **Open**. Rotating means making a new key, putting it where you use it (including your CI's secrets), then deleting the old one.

### 4 Verify

| Row | What it does |
|---|---|
| **Scan the whole Mac again** | **Scan Now** runs a full scan, so anything still there shows up again. |
| **Check the old … is dead** | **Check** asks the company that issued the key whether it still works. See [Check an old key is dead](#check-an-old-key-is-dead). |
| **Check Apple's XProtect is current** | **Check** shows XProtect's rules version and when it was installed, XProtect Remediator's version and when it last ran (from the system log of the last 3 days, read only when you press **Check**), and whether automatic security updates are on. |

Rows Watch can't do for you have an **I've done this** checkbox. The ticks are your own notes: Watch doesn't check them.

## Revoke GitHub tokens

Watch finds GitHub tokens in files on this Mac (`ghp_`, `github_pat_`, `gho_`, `ghu_` and `ghr_` tokens) and can ask GitHub's revoke service to switch them off. GitHub's service works for any token, including ones you can't sign in to revoke. Nothing is sent until you confirm.

1. Click **Revoke…** on the Revoke row. The sheet lists each token, masked, with where it is.

2. Tick the tokens to revoke. A token starts unticked, with the reason, when it fails GitHub's checksum ("Fails GitHub's checksum: probably a test value") or is only in a project folder ("Only in a project folder: may be a sample"): fixtures and cloned repositories often hold sample tokens. You can tick it yourself.

3. Read the three notes: "This can't be undone. GitHub can't switch a revoked token back on; you'll make new ones where you need them." "GitHub emails each token's owner to say it was revoked." And: Watch sends each token you tick to GitHub (`api.github.com`) over HTTPS, directly and never through a proxy, and nowhere else. Nothing goes to Subcloak.

4. Tick **I understand: the tokens I tick stop working everywhere, and this can't be undone**, then click **Revoke Token** (or **Revoke N Tokens**).

*[Screenshot: The Revoke these GitHub tokens? sheet: two tokens ticked, one unticked because it fails GitHub's checksum and one because it's only in a project folder, the three notes, the I understand checkbox, 58 of GitHub's 60 revoke requests an hour left, and Cancel and Revoke 2 Tokens buttons]*

- GitHub allows 60 revoke requests an hour, and each request can carry up to 1,000 tokens. The sheet says how many are left this hour. Watch keeps only the times of its requests, never the tokens, to count the hour.
- Only github.com tokens are sent. A token a credential store files under another host (in `~/.git-credentials`, the GitHub CLI's `hosts.yml` or `~/.netrc`) is listed as "issued by" that host, and never sent.
- When GitHub accepts, Watch says "GitHub accepted N tokens. It emails each owner. Check below that they're dead."

> **Caution:**
> Revoking a token breaks everything that uses it, including CI jobs and deploy scripts. Make the new token first if you can't afford the gap, but don't wait long: a stolen token works until it's revoked.

## Check an old key is dead

After you rotate a key, **Check** on its Verify row sends that one key to the official service of the company that issued it, to ask whether it still works. It goes directly, never through a proxy and without following redirects, and nowhere else. Nothing goes to Subcloak.

| Issuer | Where Watch asks |
|---|---|
| GitHub | `api.github.com` (`/user`) |
| npm | `registry.npmjs.org` (`/-/whoami`) |
| Anthropic | `api.anthropic.com` (`/v1/models`) |
| OpenAI | `api.openai.com` (`/v1/models`) |
| Hugging Face | `huggingface.co` |
| GitLab | `gitlab.com` |
| Slack | `slack.com` (`auth.test`) |
| AWS | `sts.amazonaws.com` (`GetCallerIdentity`), signed with the key's secret: the access key ID and the signature are sent, the secret itself isn't |

| Answer | Meaning |
|---|---|
| "… says this key is no longer valid." (green tick) | The issuer refused the key. It's dead. |
| "… still accepts this key. Rotate it, then check again." | The old key still works: delete it at the issuer. |
| Couldn't tell | The issuer asked Watch to slow down, answered something that doesn't say either way, or couldn't be reached. Try again later. |

Watch reads the key when you press **Check** and keeps it in memory until you close the sheet, so checking again after you've replaced it in its file still tests the old key. It's never written anywhere.

Some keys are never checked, and their rows say why:

- **Keys another host issued.** GitHub Enterprise Server and self-managed GitLab tokens are labelled "Issued by" that host and never sent to `github.com` or `gitlab.com`.
- **Keys that look like OpenAI's but aren't.** Only `sk-proj-`, `sk-svcacct-`, `sk-admin-` and older keys with OpenAI's marker go to OpenAI. DeepSeek, Moonshot, DashScope and LiteLLM keys look the same; check those with the service you made them for.

## After the plan

- Watch keeps watching. Anything that comes back, such as a launch item recreated by a program you didn't stop, raises a new card.
- Look at the [Feed](https://docs.subcloak.com/cloak-watch/feed/) headline for the incident, if there is one: it lists what the attack took and anything else to check.
- If something ran as administrator, rotating isn't enough: back up your files and reinstall macOS.

## From Terminal

`watchctl playbook` prints the same four steps for a fresh scan. It only reads: it never stops, revokes or sends anything, and without the app's feed it lists only what the built-in checks found. See [watchctl playbook](https://docs.subcloak.com/cloak-watch/watchctl/#playbook).

## Related

  - [Home, Inbox and incidents](https://docs.subcloak.com/cloak-watch/inbox/): Fix it and Fix all.
  - [Watch mode](https://docs.subcloak.com/cloak-watch/watch-mode/): How Watch finds programs running from stealers' paths.
  - [Quarantine and restore](https://docs.subcloak.com/cloak-watch/quarantine/): Where fixed files go, and how to put them back.
  - [What leaves your Mac](https://docs.subcloak.com/cloak-watch/settings/#what-leaves-your-mac): The revoke and key-check rows.
