> For the complete documentation index, see [llms.txt](https://docs.stepsecurity.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.stepsecurity.io/developer-machines/installation-script/mdm-deployment/macos.md).

# macOS

Dev Machine Guard runs on macOS through the [Installation Script](/developer-machines/installation-script.md) loader, which can be pushed to your fleet via any macOS MDM that supports script execution.

### Supported deployment tools

* [**Iru (formerly Kandji)**](https://claude.ai/developer-machines/installation-script/mdm-deployment/macos/iru.md) — full guide

{% hint style="info" %}
The script-based delivery model is the same across every macOS MDM listed above. If your MDM is not yet covered by a dedicated guide, the **"Where to start today"** section below shows the general pattern.
{% endhint %}

### Where to start today

If your MDM does not yet have a dedicated guide on this page:

1. Open the [**Installation Script**](/developer-machines/installation-script.md) page in the StepSecurity dashboard.
2. Select the **macOS** tab and copy the loader script (or click **Download**).
3. Deploy the script through your MDM using its standard script-distribution mechanism. For example: a Jamf Pro Policy with a Script payload, a Mosyle Custom Command, or an Intune Shell Script assignment.
4. Schedule it to run periodically (typically once daily) using your MDM's scheduling primitives.

Devices will appear in the StepSecurity dashboard under **Developer Machines →** [**Devices**](/developer-machines/devices.md) within a few minutes of the first successful run.

## macOS TCC permissions

macOS **Transparency, Consent, and Control** (TCC) is Apple's per-app permission system. It gates access to user data folders (`~/Documents`, `~/Downloads`, `~/Desktop`, `~/Pictures`, the Mail, Messages, and Safari libraries, iCloud Drive, removable volumes, and more). This section covers how Dev Machine Guard handles TCC and what to configure on a fleet so the agent can scan those folders without prompting users.

### Default behavior: skip TCC-protected paths

The agent ships with safe defaults: every scan (the `send-telemetry` run from launchd and direct CLI runs alike) skips the well-known macOS TCC-protected directories. This has two effects:

* The agent never triggers a TCC permission popup. End users do not see a "stepsecurity-dev-machine-guard would like to access files in your Documents folder" dialog.
* Anything under a TCC-protected path (a Node.js project in `~/Documents/code`, a venv under `~/Desktop/scratch`, an `.npmrc` in `~/Downloads`) is not scanned.

For most fleets this is the right trade-off, since developer code typically lives under `~/code`, `~/src`, or `~/work` rather than in `~/Documents`. Customers who want full coverage should grant the agent Full Disk Access via an MDM-pushed PPPC profile (recommended) or via System Settings on each machine, then set `include_tcc_protected` to `true`.

**What gets skipped**

The skip list is anchored at the logged-in user's `$HOME` and covers the well-known TCC categories on modern macOS:

```
~/Desktop      ~/Movies     ~/Public
~/Documents    ~/Music      ~/Library
~/Downloads    ~/.Trash
~/Pictures     /Volumes/.timemachine*   (Time Machine local snapshots, prefix match)
```

`~/Library` is skipped wholesale rather than per-subpath. Every macOS release adds new Apple-managed subtrees behind new TCC services, so a curated allowlist of `Library/X` entries goes stale on every upgrade and prompts start firing at end users again. `~/Library` is also the wrong place for developer projects, lockfiles, or `.npmrc` files. The detectors that do need specific paths under `~/Library` (JetBrains plugins at `~/Library/Application Support/JetBrains/...`, the Claude desktop MCP config, the pip global config) use targeted read calls that bypass the skip list, so they keep working unchanged.

If a search directory is explicitly named (for example `--search-dirs ~/Documents`), the walk root itself is honored. The skip only applies to TCC paths encountered as descendants of the walked root.

### Toggling the behavior

Three places can set the toggle. A CLI flag wins over the persistent config, which wins over the default.

**CLI flag (single run)**

```bash
# Default: TCC paths skipped, no popups
stepsecurity-dev-machine-guard --pretty --enable-npm-scan

# Opt in to scanning TCC paths for this run
stepsecurity-dev-machine-guard --pretty --enable-npm-scan --include-tcc-protected

# Explicit skip (even if config says otherwise)
stepsecurity-dev-machine-guard --pretty --enable-npm-scan --no-include-tcc-protected
```

**Persistent config (`~/.stepsecurity/config.json`)**

```json
{
  "customer_id": "your-customer-id",
  "api_endpoint": "https://api.stepsecurity.io",
  "api_key": "step_…",
  "scan_frequency_hours": "4",
  "include_tcc_protected": true
}
```

The agent reads this on every run. On an MDM-deployed fleet, the StepSecurity loader script (the `.sh` file the dashboard generates for each customer) writes `config.json` on every periodic tick. To roll out `include_tcc_protected` across a fleet, either edit the loader script's `write_config()` heredoc before deploying it via MDM, or have admins write the field into `~/.stepsecurity/config.json` directly on each machine (for example, via a Configuration Profile or a file-deployment mechanism).

### Granting Full Disk Access

Setting `include_tcc_protected: true` only tells the agent not to self-censor. macOS still enforces TCC: without a grant, reads in protected directories fail silently with `EACCES`. For the agent to see the contents, it needs Full Disk Access (FDA).

There are two ways to grant FDA.

#### **Option A: MDM-pushed PPPC profile (recommended for fleets)**

**Apple's** **Privacy Preferences Policy Control (PPPC)** payload lets MDM admins pre-approve specific binaries for specific TCC services. The end user sees nothing, and the grant is in place the moment the device checks in with the MDM. This is the only way to grant FDA at scale without per-user clicks.

**Inputs you need:**

The install path of the binary. The loader installs at `~/.stepsecurity/bin/stepsecurity-dev-machine-guard`, which is per-user (`/Users/<username>/.stepsecurity/bin/...`). PPPC's `Identifier` field takes an absolute filesystem path when `IdentifierType` is `path` (it has no `$HOME` or variable expansion), so you either:

* Scope a per-user profile that substitutes each user's home path, using your MDM's per-user variables (Jamf's `$HOME`-substituting profile variables, Kandji's user-context blueprints, Intune's per-user assignment, and so on), or
* Have the operator set the install directory (under Advanced Configuration) to a fixed system-wide path (for example `/usr/local/stepsecurity` , which will install the binary at `/usr/local/stepsecurity/bin/stepsecurity-dev-machine-guard` ) so the same profile applies to every user on the device.

The code requirement string derived from the binary's signature. PPPC pairs the install path with this requirement so an impostor binary at the same path cannot claim the grant. Generate it with:

```bash
codesign -d -r- /path/to/stepsecurity-dev-machine-guard 2>&1 | sed -n 's/^designated => //p'
```

You will get a line like:

```
identifier "stepsecurity-dev-machine-guard" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "D63S9HLM4L"
```

**PPPC profile XML:** Most MDMs (Jamf Pro, Kandji, Intune for macOS, JumpCloud, Mosyle, SimpleMDM, and others) accept a `.mobileconfig` profile or a JSON equivalent they convert. The relevant payload type is `com.apple.TCC.configuration-profile-policy`. A minimal profile granting **SystemPolicyAllFiles** (Full Disk Access) to the agent:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
    <key>PayloadIdentifier</key>
    <string>io.stepsecurity.dmg.tcc</string>
    <key>PayloadUUID</key>
    <string>REPLACE-WITH-UUIDGEN-OUTPUT</string>
    <key>PayloadDisplayName</key>
    <string>StepSecurity Dev Machine Guard — Full Disk Access</string>
    <key>PayloadScope</key>
    <string>System</string>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>PayloadType</key>
            <string>com.apple.TCC.configuration-profile-policy</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>io.stepsecurity.dmg.tcc.pppc</string>
            <key>PayloadUUID</key>
            <string>REPLACE-WITH-UUIDGEN-OUTPUT</string>
            <key>Services</key>
            <dict>
                <key>SystemPolicyAllFiles</key>
                <array>
                    <dict>
                        <key>Identifier</key>
                        <string>/Users/REPLACE_USERNAME/.stepsecurity/bin/stepsecurity-dev-machine-guard</string>
                        <key>IdentifierType</key>
                        <string>path</string>
                        <key>CodeRequirement</key>
                        <string>anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "D63S9HLM4L"</string>
                        <key>Allowed</key>
                        <true/>
                        <key>Comment</key>
                        <string>Allow Dev Machine Guard to scan all files for dev-tool inventory and supply-chain checks.</string>
                    </dict>
                </array>
            </dict>
        </dict>
    </array>
</dict>
</plist>
```

Replace the following:

* Both `REPLACE-WITH-UUIDGEN-OUTPUT` values with fresh UUIDs (run `uuidgen` on macOS).
* `REPLACE_USERNAME` with the target user's short username so the `Identifier` resolves to the actual on-disk binary path. For per-user MDM scoping, use your MDM's per-user variable instead of a literal username (for example, Jamf's `$USERNAME` or Kandji's user-context variable). For a fixed system-wide install, replace the whole `Identifier` value with the absolute path you chose (for example `/usr/local/stepsecurity/bin/stepsecurity-dev-machine-guard`).
* `REPLACE_TEAM_ID` with the Apple Developer Team ID embedded in the binary's code requirement (the trailing `subject.OU` field from the `codesign -d -r-` output above).

**Push the profile.**

| MDM                    | Path                                                                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Jamf Pro**           | Computers → Configuration Profiles → New → Upload → select the `.mobileconfig` file. Scope to a Smart Group containing developer machines. |
| **Kandji**             | Library → Add new → Custom Profile → upload `.mobileconfig`. Assign the Blueprint that targets developer devices.                          |
| **Intune (Microsoft)** | Devices → Configuration → Create → macOS → Templates → Custom → upload the `.mobileconfig`. Assign to a device group.                      |
| **Mosyle**             | Management → Profiles → Add → Custom → upload `.mobileconfig`.                                                                             |
| **JumpCloud**          | MDM → Policies → Custom Mac Profile → upload.                                                                                              |

The profile takes effect on the next MDM check-in (usually within minutes). Verify with:

```bash
# On a managed Mac:
profiles list -all | grep -i stepsecurity
# Or open System Settings → Privacy & Security → Full Disk Access
# and confirm "stepsecurity-dev-machine-guard" is listed and toggled on.
```

#### **Option B: Manual grant per machine**

For dev-only or single-machine testing, grant FDA manually:

1. Open System Settings → Privacy & Security → Full Disk Access.
2. Click `+`, then navigate to `~/.stepsecurity/bin/stepsecurity-dev-machine-guard`. Use \<kbd>Cmd\</kbd>+\<kbd>Shift\</kbd>+\<kbd>.\</kbd> in the file picker to show the `.stepsecurity` dotfolder.
3. Toggle the entry on.

The grant is tied to the binary's code signature. If you upgrade the binary (the loader's auto-update runs on every periodic tick), the existing grant carries over as long as the signing identity is unchanged. Dev Machine Guard releases are signed by the same Apple Developer Team for the life of each major version, so manual grants survive upgrades within that line.

### Full rollout

A fleet rollout that scans TCC paths typically looks like this:

1. Your MDM deploys the loader script (downloaded from the StepSecurity dashboard for your customer ID).
2. Your MDM also deploys the PPPC profile (Option A above) granting the agent Full Disk Access.
3. The loader's generated `config.json` includes `"include_tcc_protected": true`. Either:
   * Edit the loader script's `write_config()` heredoc to emit the field before deploying via MDM, or
   * Push a config file alongside the loader (drop it into `~/.stepsecurity/config.json` via your MDM's file-deploy mechanism).

After the next periodic fire, the agent runs with full coverage and no popups.

### Troubleshooting: a popup appears anyway

If a popup appears after deploying the PPPC profile and setting `include_tcc_protected: true`, the typical causes are:

* **Code requirement mismatch.** The PPPC profile's `CodeRequirement` string must match the binary's actual signing. Re-run `codesign -d -r-` against the deployed binary and update the profile.
* **Binary path mismatch.** If `IdentifierType=path` is used, the `Identifier` must match the absolute path of the binary on disk. Different per-user install directories can require deploying the profile per user, or matching on the code requirement alone.
* **TCC cache.** TCC caches decisions. After changing a profile, reset the relevant service so macOS re-evaluates against the latest profile on the next access:

```bash
  sudo tccutil reset SystemPolicyAllFiles
```

The agent does not call `tccutil` on its own; this is a diagnostic step only.

* **`include_tcc_protected` not actually set.** Verify with `cat ~/.stepsecurity/config.json` and re-run the loader's `write_config` step if the field is missing.

{% hint style="info" %}
For the full schema of the PPPC payload, see [Apple's PrivacyPreferencesPolicyControl documentation](https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.stepsecurity.io/developer-machines/installation-script/mdm-deployment/macos.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
